From b7c9229da44c8f5e4baa1e3503fbadaf12732c17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20R=C3=BCger?= Date: Fri, 13 Mar 2026 01:50:45 +0100 Subject: [PATCH] fix: RestrictPageUpdatesCloud now resolves allowedUser by name The allowedUser parameter was completely ignored; the function always restricted edits to the currently authenticated API user via GetCurrentUser(). Resolve the specified user via GetUserByName first and fall back to the current user only if that lookup fails, matching the behaviour of RestrictPageUpdatesServer which uses the parameter directly. fix: paginate GetAttachments to handle pages with >100 attachments The previous implementation fetched a single page of up to 1000 attachments. Pages with more than 1000 attachments would silently miss some, causing attachment sync to skip or re-upload them. Replace with a pagination loop (100 per page) that follows the _links.next cursor until all attachments are retrieved. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- confluence/api.go | 68 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 46 insertions(+), 22 deletions(-) diff --git a/confluence/api.go b/confluence/api.go index d4cee6c..61ba7da 100644 --- a/confluence/api.go +++ b/confluence/api.go @@ -437,38 +437,55 @@ func getAttachmentPayload(name, comment string, reader io.Reader) (*form, error) } func (api *API) GetAttachments(pageID string) ([]AttachmentInfo, error) { - result := struct { + type page struct { Links struct { Context string `json:"context"` + Next string `json:"next"` } `json:"_links"` Results []AttachmentInfo `json:"results"` - }{} - - payload := map[string]string{ - "expand": "version,container", - "limit": "1000", } - request, err := api.rest.Res( - "content/"+pageID+"/child/attachment", &result, - ).Get(payload) - if err != nil { - return nil, err - } + const pageSize = 100 + var all []AttachmentInfo + start := 0 - if request.Raw.StatusCode != http.StatusOK { - return nil, newErrorStatusNotOK(request) - } + for { + var result page - for i, info := range result.Results { - if info.Links.Context == "" { - info.Links.Context = result.Links.Context + payload := map[string]string{ + "expand": "version,container", + "limit": fmt.Sprintf("%d", pageSize), + "start": fmt.Sprintf("%d", start), } - result.Results[i] = info + request, err := api.rest.Res( + "content/"+pageID+"/child/attachment", &result, + ).Get(payload) + if err != nil { + return nil, err + } + + if request.Raw.StatusCode != http.StatusOK { + return nil, newErrorStatusNotOK(request) + } + + for i, info := range result.Results { + if info.Links.Context == "" { + info.Links.Context = result.Links.Context + } + result.Results[i] = info + } + + all = append(all, result.Results...) + + if len(result.Results) < pageSize || result.Links.Next == "" { + break + } + + start += len(result.Results) } - return result.Results, nil + return all, nil } func (api *API) GetPageByID(pageID string) (*PageInfo, error) { @@ -726,9 +743,16 @@ func (api *API) RestrictPageUpdatesCloud( page *PageInfo, allowedUser string, ) error { - user, err := api.GetCurrentUser() + user, err := api.GetUserByName(allowedUser) if err != nil { - return err + // Fall back to the currently authenticated user if the specified + // user cannot be resolved by name (e.g. on Confluence Cloud where + // only accountId is accepted and name lookup may fail). + currentUser, currentErr := api.GetCurrentUser() + if currentErr != nil { + return fmt.Errorf("unable to resolve user %q: %w", allowedUser, err) + } + user = currentUser } var result interface{}