Documentation
¶
Overview ¶
Package confluence is a small client for the Confluence Cloud REST API, covering exactly the calls atlio needs. It is hand-rolled rather than built on a third-party library because the page body has to survive as an untouched string end to end — a model layer that re-encodes it would break the lossless round-trip that is the whole point of this tool.
Capabilities are split across API versions as Atlassian publishes them: pages, children and comments come from v2, while CQL search, the credential check and attachment upload only exist in v1.
Index ¶
- Variables
- type Attachment
- type Child
- type Client
- func (c *Client) BaseURL() string
- func (c *Client) ChildPages(ctx context.Context, id string, limit int) ([]Child, bool, error)
- func (c *Client) CommentReplies(ctx context.Context, kind CommentKind, commentID string, limit int) ([]Comment, bool, error)
- func (c *Client) CreateFooterComment(ctx context.Context, pageID, body string) (*Comment, error)
- func (c *Client) CreatePage(ctx context.Context, req CreatePageRequest) (*Page, error)
- func (c *Client) CurrentUser(ctx context.Context) (User, error)
- func (c *Client) DownloadAttachment(ctx context.Context, downloadLink string, w io.Writer) (int64, error)
- func (c *Client) GetPage(ctx context.Context, id string, withBody bool) (*Page, error)
- func (c *Client) PageAttachments(ctx context.Context, pageID string, limit int) ([]Attachment, bool, error)
- func (c *Client) PageComments(ctx context.Context, kind CommentKind, pageID string, limit int) ([]Comment, bool, error)
- func (c *Client) PagesByTitle(ctx context.Context, spaceKey string, titles []string) ([]PageMatch, error)
- func (c *Client) Search(ctx context.Context, cql string, limit int) ([]SearchResult, int, error)
- func (c *Client) SpaceHomepageID(ctx context.Context, key string) (string, error)
- func (c *Client) UpdatePage(ctx context.Context, req UpdatePageRequest) (*Page, error)
- func (c *Client) UploadAttachments(ctx context.Context, pageID string, files []UploadFile, replace bool) ([]UploadedAttachment, error)
- func (c *Client) Users(ctx context.Context, accountIDs []string) ([]User, error)
- type Comment
- type CommentKind
- type CreatePageRequest
- type Page
- type PageMatch
- type SearchResult
- type UpdatePageRequest
- type UploadFile
- type UploadedAttachment
- type User
- type Version
Constants ¶
This section is empty.
Variables ¶
var ErrUploadRefused = errors.New("the page may already have an attachment with one of these names")
ErrUploadRefused marks a 400 to an upload that does not replace, which is how Confluence refuses a name the page already has an attachment under. It is worded as a possibility because a 400 is not only that: the server's own message, which the error carries too, is what names the cause.
var ErrUploadUnconfirmed = atlassian.ErrSentInFull
ErrUploadUnconfirmed marks an upload that failed after its whole body was sent, so Confluence may have stored the files anyway. It is the transport's atlassian.ErrSentInFull under the name of what it means here, so callers of this package need not reach into the transport to recognise it.
Functions ¶
This section is empty.
Types ¶
type Attachment ¶
type Attachment struct {
Title string `json:"title"`
MediaType string `json:"mediaType"`
FileSize int64 `json:"fileSize"`
DownloadLink string `json:"downloadLink"`
}
Attachment is one file attached to a page. Title is the filename, which is what `confluence attachments download` matches its glob against. DownloadLink is relative to the client's base URL (<site>/wiki), in the form /rest/api/content/{pageID}/child/attachment/{attachmentID}/download.
The API's own attachment id is deliberately not modelled: DownloadLink already carries it, and it is nothing a caller could use — downloads select by filename, and Confluence keeps same-named attachments as versions of one attachment rather than as separate ones, so no two listing entries share a name for an id to tell apart.
type Child ¶
type Child struct {
ID string `json:"id"`
Status string `json:"status"`
Title string `json:"title"`
Type string `json:"type"`
}
Child is one entry of a page's direct children. Type distinguishes pages from whiteboards, folders, databases and embeds. The API returns no space for a child, so callers that need one read it off the parent page.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client talks to one Confluence Cloud site with one set of credentials.
func New ¶
New creates a Client for the Atlassian site at siteURL, its scheme and host (e.g. "https://example.atlassian.net"), authenticating with Atlassian Basic auth (account email + API token). Confluence Cloud serves its API and its pages under /wiki on every site, so the client derives that base itself rather than having it stored with the site.
func (*Client) BaseURL ¶
BaseURL returns the site's Confluence base URL, <site>/wiki. Page links the API returns are relative to it, so it is what callers join them onto.
func (*Client) ChildPages ¶
ChildPages lists a page's direct child pages, oldest sibling first.
It reads /pages/{id}/direct-children — which returns every hierarchical content type — and filters to pages, rather than the /pages/{id}/children endpoint that would do that server-side: the latter is the only deprecated GET in the v2 API. Filtering inside the pagination loop (as opposed to in the caller) is what keeps limit counting child *pages* rather than children of any type.
func (*Client) CommentReplies ¶
func (c *Client) CommentReplies(ctx context.Context, kind CommentKind, commentID string, limit int) ([]Comment, bool, error)
CommentReplies lists the direct replies to one comment.
func (*Client) CreateFooterComment ¶
CreateFooterComment posts body as a new footer comment on a page and returns the comment the server created.
The body travels as the storage representation, unchanged.
func (*Client) CreatePage ¶
CreatePage creates a page and returns it as the server created it, which is where its ID, version and web link come from.
func (*Client) CurrentUser ¶
CurrentUser identifies the authenticated account. It is the cheapest authenticated call available, so `auth login` uses it to validate credentials before writing anything to disk.
func (*Client) DownloadAttachment ¶
func (c *Client) DownloadAttachment(ctx context.Context, downloadLink string, w io.Writer) (int64, error)
DownloadAttachment streams the bytes behind downloadLink to w and returns how many it wrote. Errors name downloadLink as the API returned it.
The download redirects once, to Atlassian's media service on another host, whose signed URL rejects a request that also carries the site's credentials. atlassian.Client.Download is what keeps them off it, both on the redirect and for a link that already points there.
func (*Client) GetPage ¶
GetPage fetches a page. withBody requests the storage representation; callers that only need the version (the optimistic-lock pre-flight in `confluence update`) pass false to avoid pulling the whole body back.
func (*Client) PageAttachments ¶
func (c *Client) PageAttachments(ctx context.Context, pageID string, limit int) ([]Attachment, bool, error)
PageAttachments lists the files attached to a page.
func (*Client) PageComments ¶
func (c *Client) PageComments(ctx context.Context, kind CommentKind, pageID string, limit int) ([]Comment, bool, error)
PageComments lists a page's root comments of the given kind, oldest first, with bodies in the storage representation. The comment endpoints do not accept body-format=view, so rendering for humans happens locally.
func (*Client) PagesByTitle ¶
func (c *Client) PagesByTitle(ctx context.Context, spaceKey string, titles []string) ([]PageMatch, error)
PagesByTitle resolves exact page titles within one space. Titles are unique per space, so one query answers the whole batch; a title that matched nothing is simply missing from the result.
One query per space rather than one OR-ing (space, title) pairs across spaces: every hit then belongs to the space that was asked for, so the caller can attribute it by title alone without the response having to carry the space back.
func (*Client) Search ¶
Search runs a CQL query, returning at most limit results plus the total number of matches the server reports, which is what lets the caller say how many results were left out.
CQL search only exists in v1, and it pages by offset rather than cursor.
func (*Client) SpaceHomepageID ¶
SpaceHomepageID returns the ID of the homepage of the space whose key is key. It costs one request: v2 has no endpoint that takes the key in its path, so it filters the space listing by key and takes the first page of results only, which is where a key filter's one match lands.
key may also be the space's alias: a key changed in the space's settings stays the space's key, and the new one becomes the alias its URLs carry. The filter matches either, regardless of case, so the result is picked the same way. A key that names no space the token can see answers with no results rather than a 404, so that case is reported here.
func (*Client) UpdatePage ¶
UpdatePage writes a new version of a page and returns the updated page, whose version number is the authoritative new value to record locally.
func (*Client) UploadAttachments ¶
func (c *Client) UploadAttachments(ctx context.Context, pageID string, files []UploadFile, replace bool) ([]UploadedAttachment, error)
UploadAttachments attaches files to a page in one request and returns the attachments the server reports for them.
This is v1: v2 has no upload. Without replace the request is a POST, which Confluence refuses as a whole — creating none of the files — when the page already has an attachment named like one of them; with replace it is a PUT, which adds a version to such an attachment and creates the rest.
Each file part is followed by a minorEdit field of true. The API requires the field either never or once per file, and true keeps the upload from notifying the page's watchers: the edit that embeds the file is the one they should hear about. X-Atlassian-Token is the CSRF opt-out Confluence answers 403 without.
type Comment ¶
type Comment struct {
ID string `json:"id"`
Status string `json:"status"`
Title string `json:"title"`
ParentCommentID string `json:"parentCommentId"`
Version Version `json:"version"`
ResolutionStatus string `json:"resolutionStatus"`
Body struct {
Storage struct {
Value string `json:"value"`
} `json:"storage"`
} `json:"body"`
Properties struct {
InlineOriginalSelection string `json:"inlineOriginalSelection"`
} `json:"properties"`
Links links `json:"_links"`
}
Comment is one footer or inline comment. ResolutionStatus and Properties are only populated for inline comments.
type CommentKind ¶
type CommentKind string
CommentKind selects between the two comment families a page can carry.
const ( FooterComments CommentKind = "footer-comments" // InlineComments are anchored to a highlighted span of the body. InlineComments CommentKind = "inline-comments" )
type CreatePageRequest ¶
type CreatePageRequest struct {
SpaceID string `json:"spaceId"`
Status string `json:"status"`
Title string `json:"title"`
ParentID string `json:"parentId"`
Body storageBody `json:"body"`
}
CreatePageRequest is the v2 page-create payload.
NewCreatePageRequest fixes the status at "current" and the payload has no subtype field, so neither a draft nor a live doc — which `confluence update` could not write afterwards — can be asked for. CreatePage sends no query parameters either, so a private page cannot be asked for: creating one is outside what atlio does.
func NewCreatePageRequest ¶
func NewCreatePageRequest(spaceID, parentID, title, body string) CreatePageRequest
NewCreatePageRequest builds the payload that creates a published page under parentID, in spaceID, with a storage-format body.
type Page ¶
type Page struct {
ID string `json:"id"`
Status string `json:"status"`
Title string `json:"title"`
// SpaceID is what a new page is created in: the create request needs the
// space's ID, and `confluence create` reads it off the parent.
SpaceID string `json:"spaceId"`
// Subtype is "live" for a live doc and absent for a classic page. It is
// what tells the two apart offline, once `confluence read` or
// `confluence create` has recorded it in the sidecar: a live doc's editor rewrites the storage body behind
// atlio's back, which some edits have to refuse rather than silently lose.
Subtype string `json:"subtype"`
Version Version `json:"version"`
Body struct {
Storage struct {
Representation string `json:"representation"`
Value string `json:"value"`
} `json:"storage"`
} `json:"body"`
Links links `json:"_links"`
}
Page is the subset of v2's PageSingle atlio needs. Body holds the raw storage representation exactly as the API returned it.
type PageMatch ¶
PageMatch is one page a title lookup resolved to. Title is what the search API returned, which is HTML-escaped and may carry highlight markers, so comparing it to a title read out of a page body means normalising it first — format.StripHighlightMarkers does both halves.
type SearchResult ¶
type SearchResult struct {
Content *struct {
ID string `json:"id"`
Type string `json:"type"`
Status string `json:"status"`
Title string `json:"title"`
} `json:"content"`
Title string `json:"title"`
Excerpt string `json:"excerpt"`
URL string `json:"url"`
EntityType string `json:"entityType"`
LastModified string `json:"lastModified"`
}
SearchResult is one CQL hit. Content is absent for results that are not content (spaces, users), so callers fall back to the top-level fields.
type UpdatePageRequest ¶
type UpdatePageRequest struct {
ID string `json:"id"`
Status string `json:"status"`
Title string `json:"title"`
Body storageBody `json:"body"`
Version struct {
Number int `json:"number"`
Message string `json:"message"`
} `json:"version"`
}
UpdatePageRequest is the v2 page-update payload. Every field is required by the API; atlio fills them from the file's sidecar.
func NewUpdatePageRequest ¶
func NewUpdatePageRequest(id, status, title, body string, version int, message string) UpdatePageRequest
NewUpdatePageRequest builds an update payload for a storage-format body.
type UploadFile ¶
UploadFile is one file to attach: Name is the attachment's filename, and Content is read to its end as the file's bytes.
type UploadedAttachment ¶
UploadedAttachment is one attachment as an upload left it. Version is 1 for an attachment the upload created and higher for one it added a version to.