Repository: ctreminiom/go-atlassian
Pull request #233 (2023-08-12 05:27:00)

✨ Mapped more Confluence V2 endpoints


Patch:
diff --git a/confluence/internal/attachment_impl.go b/confluence/internal/attachment_impl.go
index 0f4319b..c71e9a3 100644
--- a/confluence/internal/attachment_impl.go
+++ b/confluence/internal/attachment_impl.go
@@ -13,14 +13,16 @@ import (
 )
 
 // NewAttachmentService returns a new Confluence V2 Page service
-func NewAttachmentService(client service.Connector) *AttachmentService {
+func NewAttachmentService(client service.Connector, version *AttachmentVersionService) *AttachmentService {
 	return &AttachmentService{
 		internalClient: &internalAttachmentImpl{c: client},
+		Version:        version,
 	}
 }
 
 type AttachmentService struct {
 	internalClient confluence.AttachmentConnector
+	Version        *AttachmentVersionService
 }
 
 // Get returns a specific attachment.
@@ -51,10 +53,35 @@ func (a *AttachmentService) Gets(ctx context.Context, entityID int, entityType s
 	return a.internalClient.Gets(ctx, entityID, entityType, options, cursor, limit)
 }
 
+// Delete deletes an attachment by id.
+//
+// DELETE /wiki/api/v2/attachments/{id}
+//
+// https://docs.go-atlassian.io/confluence-cloud/v2/attachments#delete-attachment
+func (a *AttachmentService) Delete(ctx context.Context, attachmentID string) (*model.ResponseScheme, error) {
+	return a.internalClient.Delete(ctx, attachmentID)
+}
+
 type internalAttachmentImpl struct {
 	c service.Connector
 }
 
+func (i *internalAttachmentImpl) Delete(ctx context.Context, attachmentID string) (*model.ResponseScheme, error) {
+
+	if attachmentID == "" {
+		return nil, model.ErrNoContentAttachmentIDError
+	}
+
+	endpoint := fmt.Sprintf("wiki/api/v2/attachments/%v", attachmentID)
+
+	request, err := i.c.NewRequest(ctx, http.MethodDelete, endpoint, "", nil)
+	if err != nil {
+		return nil, err
+	}
+
+	return i.c.Call(request, nil)
+}
+
 func (i *internalAttachmentImpl) Get(ctx context.Context, attachmentID string, versionID int, serializeIDs bool) (*model.AttachmentScheme, *model.ResponseScheme, error) {
 
 	if attachmentID == "" {
diff --git a/confluence/internal/attachment_version_impl.go b/confluence/internal/attachment_version_impl.go
new file mode 100644
index 0000000..b344d8f
--- /dev/null
+++ b/confluence/internal/attachment_version_impl.go
@@ -0,0 +1,100 @@
+package internal
+
+import (
+	"context"
+	"fmt"
+	model "github.com/ctreminiom/go-atlassian/pkg/infra/models"
+	"github.com/ctreminiom/go-atlassian/service"
+	"github.com/ctreminiom/go-atlassian/service/confluence"
+	"net/http"
+	"net/url"
+	"strconv"
+)
+
+// NewAttachmentVersionService returns a new Confluence V2 Attachment Version service
+func NewAttachmentVersionService(client service.Connector) *AttachmentVersionService {
+	return &AttachmentVersionService{
+		internalClient: &internalAttachmentVersionImpl{c: client},
+	}
+}
+
+type AttachmentVersionService struct {
+	internalClient confluence.AttachmentVersionConnector
+}
+
+// Gets returns the versions of specific attachment.
+//
+// GET /wiki/api/v2/attachments/{id}/versions
+//
+// https://docs.go-atlassian.io/confluence-cloud/v2/attachments/versions#get-attachment-versions
+func (a *AttachmentVersionService) Gets(ctx context.Context, attachmentID, cursor, sort string, limit int) (*model.AttachmentVersionPageScheme, *model.ResponseScheme, error) {
+	return a.internalClient.Gets(ctx, attachmentID, cursor, sort, limit)
+}
+
+// Get retrieves version details for the specified attachment and version number.
+//
+// GET /wiki/api/v2/attachments/{attachment-id}/versions/{version-number}
+//
+// https://docs.go-atlassian.io/confluence-cloud/v2/attachments/versions#get-attachment-version
+func (a *AttachmentVersionService) Get(ctx context.Context, attachmentID string, versionID int) (*model.DetailedVersionScheme, *model.ResponseScheme, error) {
+	return a.internalClient.Get(ctx, attachmentID, versionID)
+}
+
+type internalAttachmentVersionImpl struct {
+	c service.Connector
+}
+
+func (i *internalAttachmentVersionImpl) Gets(ctx context.Context, attachmentID, cursor, sort string, limit int) (*model.AttachmentVersionPageScheme, *model.ResponseScheme, error) {
+
+	if attachmentID == "" {
+		return nil, nil, model.ErrNoContentAttachmentIDError
+	}
+
+	query := url.Values{}
+	query.Add("limit", strconv.Itoa(limit))
+
+	if cursor != "" {
+		query.Add("cursor", cursor)
+	}
+
+	if sort != "" {
+		query.Add("sort", sort)
+	}
+
+	endpoint := fmt.Sprintf("wiki/api/v2/attachments/%v/versions?%v", attachmentID, query.Encode())
+
+	request, err := i.c.NewRequest(ctx, http.MethodGet, endpoint, "", nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	page := new(model.AttachmentVersionPageScheme)
+	response, err := i.c.Call(request, page)
+	if err != nil {
+		return nil, response, err
+	}
+
+	return page, response, nil
+}
+
+func (i *internalAttachmentVersionImpl) Get(ctx context.Context, attachmentID string, versionID int) (*model.DetailedVersionScheme, *model.ResponseScheme, error) {
+
+	if attachmentID == "" {
+		return nil, nil, model.ErrNoContentAttachmentIDError
+	}
+
+	endpoint := fmt.Sprintf("wiki/api/v2/attachments/%v/versions/%v", attachmentID, versionID)
+
+	request, err := i.c.NewRequest(ctx, http.MethodGet, endpoint, "", nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	version := new(model.DetailedVersionScheme)
+	response, err := i.c.Call(request, version)
+	if err != nil {
+		return nil, response, err
+	}
+
+	return version, response, nil
+}
diff --git a/confluence/internal/custom_content_impl.go b/confluence/internal/custom_content_impl.go
new file mode 100644
index 0000000..b5489ac
--- /dev/null
+++ b/confluence/internal/custom_content_impl.go
@@ -0,0 +1,231 @@
+package internal
+
+import (
+	"context"
+	"fmt"
+	model "github.com/ctreminiom/go-atlassian/pkg/infra/models"
+	"github.com/ctreminiom/go-atlassian/service"
+	"github.com/ctreminiom/go-atlassian/service/confluence"
+	"net/http"
+	"net/url"
+	"strconv"
+	"strings"
+)
+
+func NewCustomContentService(client service.Connector) *CustomContentService {
+
+	return &CustomContentService{
+		internalClient: &internalCustomContentServiceImpl{c: client},
+	}
+}
+
+type CustomContentService struct {
+	internalClient confluence.CustomContentConnector
+}
+
+// Gets returns all custom content for a given type.
+//
+// GET /wiki/api/v2/custom-content
+//
+// https://docs.go-atlassian.io/confluence-cloud/v2/custom-content#get-custom-content-by-type
+func (c *CustomContentService) Gets(ctx context.Context, type_ string, options *model.CustomContentOptionsScheme, cursor string, limit int) (*model.CustomContentPageScheme, *model.ResponseScheme, error) {
+	return c.internalClient.Gets(ctx, type_, options, cursor, limit)
+}
+
+// Create creates a new custom content in the given space, page, blogpost or other custom content.
+//
+// POST /wiki/api/v2/custom-content
+//
+// https://docs.go-atlassian.io/confluence-cloud/v2/custom-content#create-custom-content
+func (c *CustomContentService) Create(ctx context.Context, payload *model.CustomContentPayloadScheme) (*model.CustomContentScheme, *model.ResponseScheme, error) {
+	return c.internalClient.Create(ctx, payload)
+}
+
+// Get returns a specific piece of custom content.
+//
+// GET /wiki/api/v2/custom-content/{id}
+//
+// https://docs.go-atlassian.io/confluence-cloud/v2/custom-content#get-custom-content-by-id
+func (c *CustomContentService) Get(ctx context.Context, customContentID int, format string, versionID int) (*model.CustomContentScheme, *model.ResponseScheme, error) {
+	return c.internalClient.Get(ctx, customContentID, format, versionID)
+}
+
+// Update updates a custom content by id.
+//
+// The spaceId is always required and maximum one of pageId, blogPostId,
+//
+// or customContentId is allowed in the request body
+//
+// PUT /wiki/api/v2/custom-content/{id}
+//
+// https://docs.go-atlassian.io/confluence-cloud/v2/custom-content#update-custom-content
+func (c *CustomContentService) Update(ctx context.Context, customContentID int, payload *model.CustomContentPayloadScheme) (*model.CustomContentScheme, *model.ResponseScheme, error) {
+	return c.internalClient.Update(ctx, customContentID, payload)
+}
+
+// Delete deletes a custom content by id.
+//
+// DELETE /wiki/api/v2/custom-content/{id}
+//
+// https://docs.go-atlassian.io/confluence-cloud/v2/custom-content#delete-custom-content
+func (c *CustomContentService) Delete(ctx context.Context, customContentID int) (*model.ResponseScheme, error) {
+	return c.internalClient.Delete(ctx, customContentID)
+}
+
+type internalCustomContentServiceImpl struct {
+	c service.Connector
+}
+
+func (i *internalCustomContentServiceImpl) Gets(ctx context.Context, type_ string, options *model.CustomContentOptionsScheme, cursor string, limit int) (*model.CustomContentPageScheme, *model.ResponseScheme, error) {
+
+	if type_ == "" {
+		return nil, nil, model.ErrNoCustomContentTypeError
+	}
+
+	query := url.Values{}
+	query.Add("limit", strconv.Itoa(limit))
+
+	if cursor != "" {
+		query.Add("cursor", cursor)
+	}
+
+	if options != nil {
+
+		if len(options.IDs) != 0 {
+
+			var ids []string
+			for _, id := range options.IDs {
+				ids = append(ids, strconv.Itoa(id))
+			}
+
+			query.Add("id", strings.Join(ids, ","))
+		}
+
+		if len(options.SpaceIDs) != 0 {
+
+			var ids []string
+			for _, id := range options.IDs {
+				ids = append(ids, strconv.Itoa(id))
+			}
+
+			query.Add("space-id", strings.Join(ids, ","))
+		}
+
+		if options.Sort != "" {
+			query.Add("sort", options.Sort)
+		}
+
+		if options.BodyFormat != "" {
+			query.Add("body-format", options.BodyFormat)
+		}
+	}
+
+	endpoint := fmt.Sprintf("wiki/api/v2/custom-content?%v", query.Encode())
+
+	request, err := i.c.NewRequest(ctx, http.MethodGet, endpoint, "", nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	page := new(model.CustomContentPageScheme)
+	response, err := i.c.Call(request, page)
+	if err != nil {
+		return nil, response, err
+	}
+
+	return page, response, nil
+}
+
+func (i *internalCustomContentServiceImpl) Create(ctx context.Context, payload *model.CustomContentPayloadScheme) (*model.CustomContentScheme, *model.ResponseScheme, error) {
+
+	endpoint := "wiki/api/v2/custom-content"
+
+	request, err := i.c.NewRequest(ctx, http.MethodPost, endpoint, "", payload)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	customContent := new(model.CustomContentScheme)
+	response, err := i.c.Call(request, customContent)
+	if err != nil {
+		return nil, response, err
+	}
+
+	return customContent, response, nil
+}
+
+func (i *internalCustomContentServiceImpl) Get(ctx context.Context, customContentID int, format string, versionID int) (*model.CustomContentScheme, *model.ResponseScheme, error) {
+
+	if customContentID == 0 {
+		return nil, nil, model.ErrNoCustomContentIDError
+	}
+
+	query := url.Values{}
+
+	if format != "" {
+		query.Add("body-format", format)
+	}
+
+	if versionID != 0 {
+		query.Add("version", strconv.Itoa(versionID))
+	}
+
+	var endpoint strings.Builder
+	endpoint.WriteString(fmt.Sprintf("wiki/api/v2/custom-content/%v", customContentID))
+
+	if query.Encode() != "" {
+		endpoint.WriteString(fmt.Sprintf("?%v", query.Encode()))
+	}
+
+	request, err := i.c.NewRequest(ctx, http.MethodGet, endpoint.String(), "", nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	customContent := new(model.CustomContentScheme)
+	response, err := i.c.Call(request, customContent)
+	if err != nil {
+		return nil, response, err
+	}
+
+	return customContent, response, nil
+}
+
+func (i *internalCustomContentServiceImpl) Update(ctx context.Context, customContentID int, payload *model.CustomContentPayloadScheme) (*model.CustomContentScheme, *model.ResponseScheme, error) {
+
+	if customContentID == 0 {
+		return nil, nil, model.ErrNoCustomContentIDError
+	}
+
+	endpoint := fmt.Sprintf("wiki/api/v2/custom-content/%v", customContentID)
+
+	request, err := i.c.NewRequest(ctx, http.MethodPut, endpoint, "", payload)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	customContent := new(model.CustomContentScheme)
+	response, err := i.c.Call(request, customContent)
+	if err != nil {
+		return nil, response, err
+	}
+
+	return customContent, response, nil
+
+}
+
+func (i *internalCustomContentServiceImpl) Delete(ctx context.Context, customContentID int) (*model.ResponseScheme, error) {
+
+	if customContentID == 0 {
+		return nil, model.ErrNoCustomContentIDError
+	}
+
+	endpoint := fmt.Sprintf("wiki/api/v2/custom-content/%v", customContentID)
+
+	request, err := i.c.NewRequest(ctx, http.MethodDelete, endpoint, "", nil)
+	if err != nil {
+		return nil, err
+	}
+
+	return i.c.Call(request, nil)
+}
diff --git a/confluence/internal/space_v2_impl.go b/confluence/internal/space_v2_impl.go
index f248d48..9d2da51 100644
--- a/confluence/internal/space_v2_impl.go
+++ b/confluence/internal/space_v2_impl.go
@@ -40,6 +40,13 @@ func (s *SpaceV2Service) Get(ctx context.Context, spaceID int, descriptionFormat
 	return s.internalClient.Get(ctx, spaceID, descriptionFormat)
 }
 
+// Permissions returns space permissions for a specific space.
+//
+// GET /wiki/api/v2/spaces/{id}/permissions
+func (s *SpaceV2Service) Permissions(ctx context.Context, spaceID int, cursor string, limit int) (*model.SpacePermissionPageScheme, *model.ResponseScheme, error) {
+	return s.internalClient.Permissions(ctx, spaceID, cursor, limit)
+}
+
 func NewSpaceV2Service(client service.Connector) *SpaceV2Service {
 
 	return &SpaceV2Service{
@@ -51,6 +58,35 @@ type internalSpaceV2Impl struct {
 	c service.Connector
 }
 
+func (i *internalSpaceV2Impl) Permissions(ctx context.Context, spaceID int, cursor string, limit int) (*model.SpacePermissionPageScheme, *model.ResponseScheme, error) {
+
+	if spaceID == 0 {
+		return nil, nil, model.ErrNoSpaceIDError
+	}
+
+	query := url.Values{}
+	query.Add("limit", strconv.Itoa(limit))
+
+	if cursor != "" {
+		query.Add("cursor", cursor)
+	}
+
+	endpoint := fmt.Sprintf("wiki/api/v2/spaces/%v/permissions?%v", spaceID, query.Encode())
+
+	request, err := i.c.NewRequest(ctx, http.MethodGet, endpoint, "", nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	chunk := new(model.SpacePermissionPageScheme)
+	response, err := i.c.Call(request, chunk)
+	if err != nil {
+		return nil, response, err
+	}
+
+	return chunk, response, nil
+}
+
 func (i *internalSpaceV2Impl) Bulk(ctx context.Context, options *model.GetSpacesOptionSchemeV2, cursor string, limit int) (*model.SpaceChunkV2Scheme, *model.ResponseScheme, error) {
 
 	query := url.Values{}
diff --git a/confluence/v2/api_client_impl.go b/confluence/v2/api_client_impl.go
index 508a663..df4e1b9 100644
--- a/confluence/v2/api_client_impl.go
+++ b/confluence/v2/api_client_impl.go
@@ -41,18 +41,20 @@ func New(httpClient common.HttpClient, site string) (*Client, error) {
 	client.Auth = internal.NewAuthenticationService(client)
 	client.Page = internal.NewPageService(client)
 	client.Space = internal.NewSpaceV2Service(client)
-	client.Attachment = internal.NewAttachmentService(client)
+	client.Attachment = internal.NewAttachmentService(client, internal.NewAttachmentVersionService(client))
+	client.CustomContent = internal.NewCustomContentService(client)
 
 	return client, nil
 }
 
 type Client struct {
-	HTTP       common.HttpClient
-	Site       *url.URL
-	Auth       common.Authentication
-	Page       *internal.PageService
-	Space      *internal.SpaceV2Service
-	Attachment *internal.AttachmentService
+	HTTP          common.HttpClient
+	Site          *url.URL
+	Auth          common.Authentication
+	Page          *internal.PageService
+	Space         *internal.SpaceV2Service
+	Attachment    *internal.AttachmentService
+	CustomContent *internal.CustomContentService
 }
 
 func (c *Client) NewRequest(ctx context.Context, method, urlStr, type_ string, body interface{}) (*http.Request, error) {
diff --git a/pkg/infra/models/confluence_attachment.go b/pkg/infra/models/confluence_attachment.go
index 18a188d..c63c3e7 100644
--- a/pkg/infra/models/confluence_attachment.go
+++ b/pkg/infra/models/confluence_attachment.go
@@ -12,17 +12,23 @@ type AttachmentScheme struct {
 	Status               string                   `json:"status,omitempty"`
 	FileSize             int                      `json:"fileSize,omitempty"`
 	MediaType            string                   `json:"mediaType,omitempty"`
-	PageID               int                      `json:"pageId,omitempty"`
+	PageID               string                   `json:"pageId,omitempty"`
 	FileID               string                   `json:"fileId,omitempty"`
 	Version              *AttachmentVersionScheme `json:"version,omitempty"`
 }
 
 type AttachmentVersionScheme struct {
-	CreatedAt string `json:"createdAt,omitempty"`
-	Message   string `json:"message,omitempty"`
-	Number    int    `json:"number,omitempty"`
-	MinorEdit bool   `json:"minorEdit,omitempty"`
-	AuthorID  string `json:"authorId,omitempty"`
+	CreatedAt  string                       `json:"createdAt,omitempty"`
+	Message    string                       `json:"message,omitempty"`
+	Number     int                          `json:"number,omitempty"`
+	MinorEdit  bool                         `json:"minorEdit,omitempty"`
+	AuthorID   string                       `json:"authorId,omitempty"`
+	Attachment *AttachmentVersionBodyScheme `json:"attachment,omitempty"`
+}
+
+type AttachmentVersionBodyScheme struct {
+	Title string `json:"title,omitempty"`
+	ID    string `json:"id,omitempty"`
 }
 
 type AttachmentParamsScheme struct {
@@ -45,10 +51,27 @@ type AttachmentParamsScheme struct {
 }
 
 type AttachmentPageScheme struct {
-	Results []*AttachmentScheme        `json:"results,omitempty"`
-	Links   *AttachmentPageLinksScheme `json:"_links,omitempty"`
+	Results []*AttachmentScheme `json:"results,omitempty"`
+	Links   *PageLinkScheme     `json:"_links,omitempty"`
 }
 
-type AttachmentPageLinksScheme struct {
+type PageLinkScheme struct {
 	Next string `json:"next,omitempty"`
 }
+
+type AttachmentVersionPageScheme struct {
+	Results []*AttachmentVersionScheme `json:"results,omitempty"`
+	Links   *PageLinkScheme            `json:"_links,omitempty"`
+}
+
+type DetailedVersionScheme struct {
+	Number              int      `json:"number,omitempty"`
+	AuthorID            string   `json:"authorId,omitempty"`
+	Message             string   `json:"message,omitempty"`
+	CreatedAt           string   `json:"createdAt,omitempty"`
+	MinorEdit           bool     `json:"minorEdit,omitempty"`
+	ContentTypeModified bool     `json:"contentTypeModified,omitempty"`
+	Collaborators       []string `json:"collaborators,omitempty"`
+	PrevVersion         int      `json:"prevVersion,omitempty"`
+	NextVersion         int      `json:"nextVersion,omitempty"`
+}
diff --git a/pkg/infra/models/confluence_custom_content.go b/pkg/infra/models/confluence_custom_content.go
new file mode 100644
index 0000000..3c7eac8
--- /dev/null
+++ b/pkg/infra/models/confluence_custom_content.go
@@ -0,0 +1,74 @@
+package models
+
+type CustomContentOptionsScheme struct {
+	IDs        []int
+	SpaceIDs   []int
+	Sort       string
+	BodyFormat string
+}
+
+type CustomContentPageScheme struct {
+	Results []*CustomContentScheme        `json:"results,omitempty"`
+	Links   *CustomContentPageLinksScheme `json:"_links,omitempty"`
+}
+
+type CustomContentPageLinksScheme struct {
+	Next string `json:"next,omitempty"`
+}
+
+type CustomContentScheme struct {
+	ID              string                      `json:"id,omitempty"`
+	Type            string                      `json:"type,omitempty"`
+	Status          string                      `json:"status,omitempty"`
+	Title           string                      `json:"title,omitempty"`
+	SpaceID         string                      `json:"spaceId,omitempty"`
+	PageID          string                      `json:"pageId,omitempty"`
+	BlogPostID      string                      `json:"blogPostId,omitempty"`
+	CustomContentID string                      `json:"customContentId,omitempty"`
+	AuthorID        string                      `json:"authorId,omitempty"`
+	CreatedAt       string                      `json:"createdAt,omitempty"`
+	Version         *CustomContentVersionScheme `json:"version,omitempty"`
+	Body            *CustomContentBodyScheme    `json:"body,omitempty"`
+	Links           *CustomContentLinksScheme   `json:"_links,omitempty"`
+}
+
+type CustomContentVersionScheme struct {
+	CreatedAt string `json:"createdAt,omitempty"`
+	Message   string `json:"message,omitempty"`
+	Number    int    `json:"number,omitempty"`
+	MinorEdit bool   `json:"minorEdit,omitempty"`
+	AuthorID  string `json:"authorId,omitempty"`
+}
+
+type CustomContentBodyScheme struct {
+	Raw            *BodyTypeScheme `json:"raw,omitempty"`
+	Storage        *BodyTypeScheme `json:"storage,omitempty"`
+	AtlasDocFormat *BodyTypeScheme `json:"atlas_doc_format,omitempty"`
+}
+
+type CustomContentLinksScheme struct {
+	Webui string `json:"webui,omitempty"`
+}
+
+type BodyTypeScheme struct {
+	Representation string `json:"representation,omitempty"`
+	Value          string `json:"value,omitempty"`
+}
+
+type CustomContentPayloadScheme struct {
+	ID              string                             `json:"id,omitempty"`
+	Type            string                             `json:"type,omitempty"`
+	Status          string                             `json:"status,omitempty"`
+	SpaceID         string                             `json:"spaceId,omitempty"`
+	PageID          string                             `json:"pageId,omitempty"`
+	BlogPostID      string                             `json:"blogPostId,omitempty"`
+	CustomContentID string                             `json:"customContentId,omitempty"`
+	Title           string                             `json:"title,omitempty"`
+	Body            *BodyTypeScheme                    `json:"body,omitempty"`
+	Version         *CustomContentPayloadVersionScheme `json:"version,omitempty"`
+}
+
+type CustomContentPayloadVersionScheme struct {
+	Number  int    `json:"number,omitempty"`
+	Message string `json:"message,omitempty"`
+}
diff --git a/pkg/infra/models/confluence_space_v2.go b/pkg/infra/models/confluence_space_v2.go
index 3974d4a..3186af0 100644
--- a/pkg/infra/models/confluence_space_v2.go
+++ b/pkg/infra/models/confluence_space_v2.go
@@ -36,3 +36,28 @@ type SpaceDescriptionSchemeV2 struct {
 	Plain *PageBodyRepresentationScheme `json:"plain,omitempty"`
 	View  *PageBodyRepresentationScheme `json:"view,omitempty"`
 }
+
+type SpacePermissionPageScheme struct {
+	Results []*SpacePermissionsV2Scheme    `json:"results,omitempty"`
+	Links   *SpacePermissionPageLinkScheme `json:"_links,omitempty"`
+}
+
+type SpacePermissionsV2Scheme struct {
+	ID        string                           `json:"id"`
+	Principal *SpacePermissionsPrincipalScheme `json:"principal"`
+	Operation *SpacePermissionsOperationScheme `json:"operation"`
+}
+
+type SpacePermissionsPrincipalScheme struct {
+	Type string `json:"type,omitempty"`
+	ID   string `json:"id,omitempty"`
+}
+
+type SpacePermissionsOperationScheme struct {
+	Key        string `json:"key,omitempty"`
+	TargetType string `json:"targetType,omitempty"`
+}
+
+type SpacePermissionPageLinkScheme struct {
+	Next string `json:"next,omitempty"`
+}
diff --git a/pkg/infra/models/errors.go b/pkg/infra/models/errors.go
index 7055453..f69f1cd 100644
--- a/pkg/infra/models/errors.go
+++ b/pkg/infra/models/errors.go
@@ -36,6 +36,8 @@ var (
 	ErrNoContentAttachmentNameError        = errors.New("confluence: no attachment filename set")
 	ErrNoContentReaderError                = errors.New("confluence: no reader set")
 	ErrNoContentIDError                    = errors.New("confluence: no content id set")
+	ErrNoCustomContentTypeError            = errors.New("confluence: no custom content type set")
+	ErrNoCustomContentIDError              = errors.New("confluence: no custom content id set")
 	ErrNoPageIDError                       = errors.New("confluence: no page id set")
 	ErrNoSpaceIDError                      = errors.New("confluence: no space id set")
 	ErrNoTargetIDError                     = errors.New("confluence: no target id set")
diff --git a/service/confluence/attachment.go b/service/confluence/attachment.go
index 8fd5c2c..5989b1d 100644
--- a/service/confluence/attachment.go
+++ b/service/confluence/attachment.go
@@ -65,4 +65,28 @@ type AttachmentConnector interface {
 	//
 	// https://docs.go-atlassian.io/confluence-cloud/v2/attachments#get-attachments-by-type
 	Gets(ctx context.Context, entityID int, entityType string, options *model.AttachmentParamsScheme, cursor string, limit int) (*model.AttachmentPageScheme, *model.ResponseScheme, error)
+
+	// Delete deletes an attachment by id.
+	//
+	// DELETE /wiki/api/v2/attachments/{id}
+	//
+	// https://docs.go-atlassian.io/confluence-cloud/v2/attachments#delete-attachment
+	Delete(ctx context.Context, attachmentID string) (*model.ResponseScheme, error)
+}
+
+type AttachmentVersionConnector interface {
+
+	// Gets returns the versions of specific attachment.
+	//
+	// GET /wiki/api/v2/attachments/{id}/versions
+	//
+	// https://docs.go-atlassian.io/confluence-cloud/v2/attachments/versions#get-attachment-versions
+	Gets(ctx context.Context, attachmentID, cursor, sort string, limit int) (*model.AttachmentVersionPageScheme, *model.ResponseScheme, error)
+
+	// Get retrieves version details for the specified attachment and version number.
+	//
+	// GET /wiki/api/v2/attachments/{attachment-id}/versions/{version-number}
+	//
+	// https://docs.go-atlassian.io/confluence-cloud/v2/attachments/versions#get-attachment-version
+	Get(ctx context.Context, attachmentID string, versionID int) (*model.DetailedVersionScheme, *model.ResponseScheme, error)
 }
diff --git a/service/confluence/custom-content.go b/service/confluence/custom-content.go
new file mode 100644
index 0000000..6ae411c
--- /dev/null
+++ b/service/confluence/custom-content.go
@@ -0,0 +1,49 @@
+package confluence
+
+import (
+	"context"
+	"github.com/ctreminiom/go-atlassian/pkg/infra/models"
+)
+
+type CustomContentConnector interface {
+
+	// Gets returns all custom content for a given type.
+	//
+	// GET /wiki/api/v2/custom-content
+	//
+	// https://docs.go-atlassian.io/confluence-cloud/v2/custom-content#get-custom-content-by-type
+	Gets(ctx context.Context, type_ string, options *models.CustomContentOptionsScheme, cursor string, limit int) (
+		*models.CustomContentPageScheme, *models.ResponseScheme, error)
+
+	// Create creates a new custom content in the given space, page, blogpost or other custom content.
+	//
+	// POST /wiki/api/v2/custom-content
+	//
+	// https://docs.go-atlassian.io/confluence-cloud/v2/custom-content#create-custom-content
+	Create(ctx context.Context, payload *models.CustomContentPayloadScheme) (*models.CustomContentScheme, *models.ResponseScheme, error)
+
+	// Get returns a specific piece of custom content.
+	//
+	// GET /wiki/api/v2/custom-content/{id}
+	//
+	// https://docs.go-atlassian.io/confluence-cloud/v2/custom-content#get-custom-content-by-id
+	Get(ctx context.Context, customContentID int, format string, versionID int) (*models.CustomContentScheme, *models.ResponseScheme, error)
+
+	// Update updates a custom content by id.
+	//
+	// The spaceId is always required and maximum one of pageId, blogPostId,
+	//
+	// or customContentId is allowed in the request body
+	//
+	// PUT /wiki/api/v2/custom-content/{id}
+	//
+	// https://docs.go-atlassian.io/confluence-cloud/v2/custom-content#update-custom-content
+	Update(ctx context.Context, customContentID int, payload *models.CustomContentPayloadScheme) (*models.CustomContentScheme, *models.ResponseScheme, error)
+
+	// Delete deletes a custom content by id.
+	//
+	// DELETE /wiki/api/v2/custom-content/{id}
+	//
+	// https://docs.go-atlassian.io/confluence-cloud/v2/custom-content#delete-custom-content
+	Delete(ctx context.Context, customContentID int) (*models.ResponseScheme, error)
+}
diff --git a/service/confluence/space.go b/service/confluence/space.go
index f624363..12fbfaf 100644
--- a/service/confluence/space.go
+++ b/service/confluence/space.go
@@ -26,6 +26,11 @@ type SpaceV2Connector interface {
 	//
 	// https://docs.go-atlassian.io/confluence-cloud/v2/space#get-space-by-id
 	Get(ctx context.Context, spaceID int, descriptionFormat string) (*model.SpaceSchemeV2, *model.ResponseScheme, error)
+
+	// Permissions returns space permissions for a specific space.
+	//
+	// GET /wiki/api/v2/spaces/{id}/permissions
+	Permissions(ctx context.Context, spaceID int, cursor string, limit int) (*model.SpacePermissionPageScheme, *model.ResponseScheme, error)
 }
 
 type SpaceConnector interface {


Test patch:
diff --git a/confluence/internal/attachment_impl_test.go b/confluence/internal/attachment_impl_test.go
index 3ef3cdb..4228826 100644
--- a/confluence/internal/attachment_impl_test.go
+++ b/confluence/internal/attachment_impl_test.go
@@ -104,7 +104,7 @@ func Test_internalAttachmentImpl_Get(t *testing.T) {
 				testCase.on(&testCase.fields)
 			}
 
-			attachmentService := NewAttachmentService(testCase.fields.c)
+			attachmentService := NewAttachmentService(testCase.fields.c, nil)
 
 			gotResult, gotResponse, err := attachmentService.Get(testCase.args.ctx, testCase.args.attachmentID, testCase.args.versionID,
 				testCase.args.serializeIDs)
@@ -248,7 +248,7 @@ func Test_internalAttachmentImpl_Gets(t *testing.T) {
 				testCase.on(&testCase.fields)
 			}
 
-			attachmentService := NewAttachmentService(testCase.fields.c)
+			attachmentService := NewAttachmentService(testCase.fields.c, nil)
 
 			gotResult, gotResponse, err := attachmentService.Gets(testCase.args.ctx, testCase.args.entityID, testCase.args.entityType,
 				testCase.args.options, testCase.args.cursor, testCase.args.limit)
@@ -271,3 +271,112 @@ func Test_internalAttachmentImpl_Gets(t *testing.T) {
 		})
 	}
 }
+
+func Test_internalAttachmentImpl_Delete(t *testing.T) {
+
+	type fields struct {
+		c service.Connector
+	}
+
+	type args struct {
+		ctx          context.Context
+		attachmentID string
+	}
+
+	testCases := []struct {
+		name    string
+		fields  fields
+		args    args
+		on      func(*fields)
+		wantErr bool
+		Err     error
+	}{
+		{
+			name: "when the parameters are correct",
+			args: args{
+				ctx:          context.TODO(),
+				attachmentID: "att10001",
+			},
+			on: func(fields *fields) {
+
+				client := mocks.NewConnector(t)
+
+				client.On("NewRequest",
+					context.Background(),
+					http.MethodDelete,
+					"wiki/api/v2/attachments/att10001",
+					"", nil).
+					Return(&http.Request{}, nil)
+
+				client.On("Call",
+					&http.Request{},
+					nil).
+					Return(&model.ResponseScheme{}, nil)
+
+				fields.c = client
+
+			},
+		},
+
+		{
+			name: "when the http request cannot be created",
+			args: args{
+				ctx:          context.TODO(),
+				attachmentID: "att10001",
+			},
+			on: func(fields *fields) {
+
+				client := mocks.NewConnector(t)
+
+				client.On("NewRequest",
+					context.Background(),
+					http.MethodDelete,
+					"wiki/api/v2/attachments/att10001",
+					"", nil).
+					Return(&http.Request{}, errors.New("error, unable to create the http request"))
+
+				fields.c = client
+
+			},
+			wantErr: true,
+			Err:     errors.New("error, unable to create the http request"),
+		},
+
+		{
+			name: "when the attachment id is not provided",
+			args: args{
+				ctx: context.TODO(),
+			},
+			wantErr: true,
+			Err:     model.ErrNoContentAttachmentIDError,
+		},
+	}
+
+	for _, testCase := range testCases {
+		t.Run(testCase.name, func(t *testing.T) {
+
+			if testCase.on != nil {
+				testCase.on(&testCase.fields)
+			}
+
+			attachmentService := NewAttachmentService(testCase.fields.c, nil)
+
+			gotResponse, err := attachmentService.Delete(testCase.args.ctx, testCase.args.attachmentID)
+
+			if testCase.wantErr {
+
+				if err != nil {
+					t.Logf("error returned: %v", err.Error())
+				}
+
+				assert.EqualError(t, err, testCase.Err.Error())
+
+			} else {
+
+				assert.NoError(t, err)
+				assert.NotEqual(t, gotResponse, nil)
+			}
+
+		})
+	}
+}
diff --git a/confluence/internal/attachment_version_impl_test.go b/confluence/internal/attachment_version_impl_test.go
new file mode 100644
index 0000000..c3cbcac
--- /dev/null
+++ b/confluence/internal/attachment_version_impl_test.go
@@ -0,0 +1,243 @@
+package internal
+
+import (
+	"context"
+	"errors"
+	model "github.com/ctreminiom/go-atlassian/pkg/infra/models"
+	"github.com/ctreminiom/go-atlassian/service"
+	"github.com/ctreminiom/go-atlassian/service/mocks"
+	"github.com/stretchr/testify/assert"
+	"net/http"
+	"testing"
+)
+
+func Test_internalAttachmentVersionImpl_Gets(t *testing.T) {
+
+	type fields struct {
+		c service.Connector
+	}
+
+	type args struct {
+		ctx                        context.Context
+		attachmentID, cursor, sort string
+		limit                      int
+	}
+
+	testCases := []struct {
+		name    string
+		fields  fields
+		args    args
+		on      func(*fields)
+		wantErr bool
+		Err     error
+	}{
+		{
+			name: "when the parameters are correct",
+			args: args{
+				ctx:          context.TODO(),
+				attachmentID: "100001",
+				sort:         "-modified-date",
+				cursor:       "uuid-sample",
+				limit:        100,
+			},
+			on: func(fields *fields) {
+
+				client := mocks.NewConnector(t)
+
+				client.On("NewRequest",
+					context.Background(),
+					http.MethodGet,
+					"wiki/api/v2/attachments/100001/versions?cursor=uuid-sample&limit=100&sort=-modified-date",
+					"", nil).
+					Return(&http.Request{}, nil)
+
+				client.On("Call",
+					&http.Request{},
+					&model.AttachmentVersionPageScheme{}).
+					Return(&model.ResponseScheme{}, nil)
+
+				fields.c = client
+
+			},
+		},
+
+		{
+			name: "when the http request cannot be created",
+			args: args{
+				ctx:          context.TODO(),
+				attachmentID: "100001",
+				sort:         "-modified-date",
+				cursor:       "uuid-sample",
+				limit:        100,
+			},
+			on: func(fields *fields) {
+
+				client := mocks.NewConnector(t)
+
+				client.On("NewRequest",
+					context.Background(),
+					http.MethodGet,
+					"wiki/api/v2/attachments/100001/versions?cursor=uuid-sample&limit=100&sort=-modified-date",
+					"", nil).
+					Return(&http.Request{}, errors.New("error, unable to create the http request"))
+
+				fields.c = client
+
+			},
+			wantErr: true,
+			Err:     errors.New("error, unable to create the http request"),
+		},
+
+		{
+			name: "when the attachment id is not provided",
+			args: args{
+				ctx: context.TODO(),
+			},
+			wantErr: true,
+			Err:     model.ErrNoContentAttachmentIDError,
+		},
+	}
+
+	for _, testCase := range testCases {
+		t.Run(testCase.name, func(t *testing.T) {
+
+			if testCase.on != nil {
+				testCase.on(&testCase.fields)
+			}
+
+			attachmentService := NewAttachmentVersionService(testCase.fields.c)
+
+			gotResult, gotResponse, err := attachmentService.Gets(testCase.args.ctx, testCase.args.attachmentID, testCase.args.cursor,
+				testCase.args.sort, testCase.args.limit)
+
+			if testCase.wantErr {
+
+				if err != nil {
+					t.Logf("error returned: %v", err.Error())
+				}
+
+				assert.EqualError(t, err, testCase.Err.Error())
+
+			} else {
+
+				assert.NoError(t, err)
+				assert.NotEqual(t, gotResponse, nil)
+				assert.NotEqual(t, gotResult, nil)
+			}
+
+		})
+	}
+}
+
+func Test_internalAttachmentVersionImpl_Get(t *testing.T) {
+
+	type fields struct {
+		c service.Connector
+	}
+
+	type args struct {
+		ctx          context.Context
+		attachmentID string
+		versionID    int
+	}
+
+	testCases := []struct {
+		name    string
+		fields  fields
+		args    args
+		on      func(*fields)
+		wantErr bool
+		Err     error
+	}{
+		{
+			name: "when the parameters are correct",
+			args: args{
+				ctx:          context.TODO(),
+				attachmentID: "100001",
+				versionID:    10002,
+			},
+			on: func(fields *fields) {
+
+				client := mocks.NewConnector(t)
+
+				client.On("NewRequest",
+					context.Background(),
+					http.MethodGet,
+					"wiki/api/v2/attachments/100001/versions/10002",
+					"", nil).
+					Return(&http.Request{}, nil)
+
+				client.On("Call",
+					&http.Request{},
+					&model.DetailedVersionScheme{}).
+					Return(&model.ResponseScheme{}, nil)
+
+				fields.c = client
+
+			},
+		},
+
+		{
+			name: "when the http request cannot be created",
+			args: args{
+				ctx:          context.TODO(),
+				attachmentID: "100001",
+				versionID:    10002,
+			},
+			on: func(fields *fields) {
+
+				client := mocks.NewConnector(t)
+
+				client.On("NewRequest",
+					context.Background(),
+					http.MethodGet,
+					"wiki/api/v2/attachments/100001/versions/10002",
+					"", nil).
+					Return(&http.Request{}, errors.New("error, unable to create the http request"))
+
+				fields.c = client
+
+			},
+			wantErr: true,
+			Err:     errors.New("error, unable to create the http request"),
+		},
+
+		{
+			name: "when the attachment id is not provided",
+			args: args{
+				ctx: context.TODO(),
+			},
+			wantErr: true,
+			Err:     model.ErrNoContentAttachmentIDError,
+		},
+	}
+
+	for _, testCase := range testCases {
+		t.Run(testCase.name, func(t *testing.T) {
+
+			if testCase.on != nil {
+				testCase.on(&testCase.fields)
+			}
+
+			attachmentService := NewAttachmentVersionService(testCase.fields.c)
+
+			gotResult, gotResponse, err := attachmentService.Get(testCase.args.ctx, testCase.args.attachmentID, testCase.args.versionID)
+
+			if testCase.wantErr {
+
+				if err != nil {
+					t.Logf("error returned: %v", err.Error())
+				}
+
+				assert.EqualError(t, err, testCase.Err.Error())
+
+			} else {
+
+				assert.NoError(t, err)
+				assert.NotEqual(t, gotResponse, nil)
+				assert.NotEqual(t, gotResult, nil)
+			}
+
+		})
+	}
+}
diff --git a/confluence/internal/custom_content_impl_test.go b/confluence/internal/custom_content_impl_test.go
new file mode 100644
index 0000000..2211a53
--- /dev/null
+++ b/confluence/internal/custom_content_impl_test.go
@@ -0,0 +1,623 @@
+package internal
+
+import (
+	"context"
+	"errors"
+	model "github.com/ctreminiom/go-atlassian/pkg/infra/models"
+	"github.com/ctreminiom/go-atlassian/service"
+	"github.com/ctreminiom/go-atlassian/service/mocks"
+	"github.com/stretchr/testify/assert"
+	"net/http"
+	"testing"
+)
+
+func Test_internalCustomContentServiceImpl_Gets(t *testing.T) {
+
+	type fields struct {
+		c service.Connector
+	}
+
+	type args struct {
+		ctx     context.Context
+		type_   string
+		options *model.CustomContentOptionsScheme
+		cursor  string
+		limit   int
+	}
+
+	testCases := []struct {
+		name    string
+		fields  fields
+		args    args
+		on      func(*fields)
+		wantErr bool
+		Err     error
+	}{
+		{
+			name: "when the parameters are correct",
+			args: args{
+				ctx:   context.TODO(),
+				type_: "custom_content_type_id",
+				options: &model.CustomContentOptionsScheme{
+					IDs:        []int{101, 102},
+					SpaceIDs:   []int{10001, 10002},
+					Sort:       "created-date",
+					BodyFormat: "atlas_doc_format",
+				},
+				cursor: "cursor-id",
+				limit:  200,
+			},
+			on: func(fields *fields) {
+
+				client := mocks.NewConnector(t)
+
+				client.On("NewRequest",
+					context.Background(),
+					http.MethodGet,
+					"wiki/api/v2/custom-content?body-format=atlas_doc_format&cursor=cursor-id&id=101%2C102&limit=200&sort=created-date&space-id=101%2C102",
+					"", nil).
+					Return(&http.Request{}, nil)
+
+				client.On("Call",
+					&http.Request{},
+					&model.CustomContentPageScheme{}).
+					Return(&model.ResponseScheme{}, nil)
+
+				fields.c = client
+
+			},
+		},
+
+		{
+			name: "when the http request cannot be created",
+			args: args{
+				ctx:   context.TODO(),
+				type_: "custom_content_type_id",
+				options: &model.CustomContentOptionsScheme{
+					IDs:        []int{101, 102},
+					SpaceIDs:   []int{10001, 10002},
+					Sort:       "created-date",
+					BodyFormat: "atlas_doc_format",
+				},
+				cursor: "cursor-id",
+				limit:  200,
+			},
+			on: func(fields *fields) {
+
+				client := mocks.NewConnector(t)
+
+				client.On("NewRequest",
+					context.Background(),
+					http.MethodGet,
+					"wiki/api/v2/custom-content?body-format=atlas_doc_format&cursor=cursor-id&id=101%2C102&limit=200&sort=created-date&space-id=101%2C102",
+					"", nil).
+					Return(&http.Request{}, errors.New("error, unable to create the http request"))
+
+				fields.c = client
+			},
+			wantErr: true,
+			Err:     errors.New("error, unable to create the http request"),
+		},
+
+		{
+			name: "when the type is not provided are correct",
+			args: args{
+				ctx: context.TODO(),
+			},
+			wantErr: true,
+			Err:     model.ErrNoCustomContentTypeError,
+		},
+	}
+
+	for _, testCase := range testCases {
+		t.Run(testCase.name, func(t *testing.T) {
+
+			if testCase.on != nil {
+				testCase.on(&testCase.fields)
+			}
+
+			newService := NewCustomContentService(testCase.fields.c)
+
+			gotResult, gotResponse, err := newService.Gets(
+				testCase.args.ctx,
+				testCase.args.type_,
+				testCase.args.options,
+				testCase.args.cursor,
+				testCase.args.limit,
+			)
+
+			if testCase.wantErr {
+
+				if err != nil {
+					t.Logf("error returned: %v", err.Error())
+				}
+
+				assert.EqualError(t, err, testCase.Err.Error())
+
+			} else {
+
+				assert.NoError(t, err)
+				assert.NotEqual(t, gotResponse, nil)
+				assert.NotEqual(t, gotResult, nil)
+			}
+
+		})
+	}
+}
+
+func Test_internalCustomContentServiceImpl_Create(t *testing.T) {
+
+	payloadMocked := &model.CustomContentPayloadScheme{
+		Type:            "",
+		Status:          "",
+		PageID:          "",
+		CustomContentID: "",
+		Title:           "",
+		Body: &model.BodyTypeScheme{
+			Representation: "",
+			Value:          "",
+		},
+	}
+
+	type fields struct {
+		c service.Connector
+	}
+
+	type args struct {
+		ctx     context.Context
+		payload *model.CustomContentPayloadScheme
+	}
+
+	testCases := []struct {
+		name    string
+		fields  fields
+		args    args
+		on      func(*fields)
+		wantErr bool
+		Err     error
+	}{
+		{
+			name: "when the parameters are correct",
+			args: args{
+				ctx:     context.TODO(),
+				payload: payloadMocked,
+			},
+			on: func(fields *fields) {
+
+				client := mocks.NewConnector(t)
+
+				client.On("NewRequest",
+					context.Background(),
+					http.MethodPost,
+					"wiki/api/v2/custom-content",
+					"", payloadMocked).
+					Return(&http.Request{}, nil)
+
+				client.On("Call",
+					&http.Request{},
+					&model.CustomContentScheme{}).
+					Return(&model.ResponseScheme{}, nil)
+
+				fields.c = client
+
+			},
+		},
+
+		{
+			name: "when the http request cannot be created",
+			args: args{
+				ctx:     context.TODO(),
+				payload: payloadMocked,
+			},
+			on: func(fields *fields) {
+
+				client := mocks.NewConnector(t)
+
+				client.On("NewRequest",
+					context.Background(),
+					http.MethodPost,
+					"wiki/api/v2/custom-content",
+					"", payloadMocked).
+					Return(&http.Request{}, errors.New("error, unable to create the http request"))
+
+				fields.c = client
+			},
+			wantErr: true,
+			Err:     errors.New("error, unable to create the http request"),
+		},
+	}
+
+	for _, testCase := range testCases {
+		t.Run(testCase.name, func(t *testing.T) {
+
+			if testCase.on != nil {
+				testCase.on(&testCase.fields)
+			}
+
+			newService := NewCustomContentService(testCase.fields.c)
+
+			gotResult, gotResponse, err := newService.Create(
+				testCase.args.ctx,
+				testCase.args.payload,
+			)
+
+			if testCase.wantErr {
+
+				if err != nil {
+					t.Logf("error returned: %v", err.Error())
+				}
+
+				assert.EqualError(t, err, testCase.Err.Error())
+
+			} else {
+
+				assert.NoError(t, err)
+				assert.NotEqual(t, gotResponse, nil)
+				assert.NotEqual(t, gotResult, nil)
+			}
+
+		})
+	}
+}
+
+func Test_internalCustomContentServiceImpl_Update(t *testing.T) {
+
+	payloadMocked := &model.CustomContentPayloadScheme{
+		Type:            "",
+		Status:          "",
+		PageID:          "",
+		CustomContentID: "",
+		Title:           "",
+		Body: &model.BodyTypeScheme{
+			Representation: "",
+			Value:          "",
+		},
+		Version: &model.CustomContentPayloadVersionScheme{
+			Number:  1,
+			Message: "message sample",
+		},
+	}
+
+	type fields struct {
+		c service.Connector
+	}
+
+	type args struct {
+		ctx             context.Context
+		customContentID int
+		payload         *model.CustomContentPayloadScheme
+	}
+
+	testCases := []struct {
+		name    string
+		fields  fields
+		args    args
+		on      func(*fields)
+		wantErr bool
+		Err     error
+	}{
+		{
+			name: "when the parameters are correct",
+			args: args{
+				ctx:             context.TODO(),
+				customContentID: 10001,
+				payload:         payloadMocked,
+			},
+			on: func(fields *fields) {
+
+				client := mocks.NewConnector(t)
+
+				client.On("NewRequest",
+					context.Background(),
+					http.MethodPut,
+					"wiki/api/v2/custom-content/10001",
+					"", payloadMocked).
+					Return(&http.Request{}, nil)
+
+				client.On("Call",
+					&http.Request{},
+					&model.CustomContentScheme{}).
+					Return(&model.ResponseScheme{}, nil)
+
+				fields.c = client
+
+			},
+		},
+
+		{
+			name: "when the http request cannot be created",
+			args: args{
+				ctx:             context.TODO(),
+				customContentID: 10001,
+				payload:         payloadMocked,
+			},
+			on: func(fields *fields) {
+
+				client := mocks.NewConnector(t)
+
+				client.On("NewRequest",
+					context.Background(),
+					http.MethodPut,
+					"wiki/api/v2/custom-content/10001",
+					"", payloadMocked).
+					Return(&http.Request{}, errors.New("error, unable to create the http request"))
+
+				fields.c = client
+			},
+			wantErr: true,
+			Err:     errors.New("error, unable to create the http request"),
+		},
+
+		{
+			name: "when the custom content id is not provided",
+			args: args{
+				ctx: context.TODO(),
+			},
+			wantErr: true,
+			Err:     model.ErrNoCustomContentIDError,
+		},
+	}
+
+	for _, testCase := range testCases {
+		t.Run(testCase.name, func(t *testing.T) {
+
+			if testCase.on != nil {
+				testCase.on(&testCase.fields)
+			}
+
+			newService := NewCustomContentService(testCase.fields.c)
+
+			gotResult, gotResponse, err := newService.Update(
+				testCase.args.ctx,
+				testCase.args.customContentID,
+				testCase.args.payload,
+			)
+
+			if testCase.wantErr {
+
+				if err != nil {
+					t.Logf("error returned: %v", err.Error())
+				}
+
+				assert.EqualError(t, err, testCase.Err.Error())
+
+			} else {
+
+				assert.NoError(t, err)
+				assert.NotEqual(t, gotResponse, nil)
+				assert.NotEqual(t, gotResult, nil)
+			}
+
+		})
+	}
+}
+
+func Test_internalCustomContentServiceImpl_Delete(t *testing.T) {
+
+	type fields struct {
+		c service.Connector
+	}
+
+	type args struct {
+		ctx             context.Context
+		customContentID int
+	}
+
+	testCases := []struct {
+		name    string
+		fields  fields
+		args    args
+		on      func(*fields)
+		wantErr bool
+		Err     error
+	}{
+		{
+			name: "when the parameters are correct",
+			args: args{
+				ctx:             context.TODO(),
+				customContentID: 10001,
+			},
+			on: func(fields *fields) {
+
+				client := mocks.NewConnector(t)
+
+				client.On("NewRequest",
+					context.Background(),
+					http.MethodDelete,
+					"wiki/api/v2/custom-content/10001",
+					"", nil).
+					Return(&http.Request{}, nil)
+
+				client.On("Call",
+					&http.Request{},
+					nil).
+					Return(&model.ResponseScheme{}, nil)
+
+				fields.c = client
+
+			},
+		},
+
+		{
+			name: "when the http request cannot be created",
+			args: args{
+				ctx:             context.TODO(),
+				customContentID: 10001,
+			},
+			on: func(fields *fields) {
+
+				client := mocks.NewConnector(t)
+
+				client.On("NewRequest",
+					context.Background(),
+					http.MethodDelete,
+					"wiki/api/v2/custom-content/10001",
+					"", nil).
+					Return(&http.Request{}, errors.New("error, unable to create the http request"))
+
+				fields.c = client
+			},
+			wantErr: true,
+			Err:     errors.New("error, unable to create the http request"),
+		},
+
+		{
+			name: "when the custom content id is not provided",
+			args: args{
+				ctx: context.TODO(),
+			},
+			wantErr: true,
+			Err:     model.ErrNoCustomContentIDError,
+		},
+	}
+
+	for _, testCase := range testCases {
+		t.Run(testCase.name, func(t *testing.T) {
+
+			if testCase.on != nil {
+				testCase.on(&testCase.fields)
+			}
+
+			newService := NewCustomContentService(testCase.fields.c)
+
+			gotResponse, err := newService.Delete(
+				testCase.args.ctx,
+				testCase.args.customContentID)
+
+			if testCase.wantErr {
+
+				if err != nil {
+					t.Logf("error returned: %v", err.Error())
+				}
+
+				assert.EqualError(t, err, testCase.Err.Error())
+
+			} else {
+
+				assert.NoError(t, err)
+				assert.NotEqual(t, gotResponse, nil)
+			}
+
+		})
+	}
+}
+
+func Test_internalCustomContentServiceImpl_Get(t *testing.T) {
+
+	type fields struct {
+		c service.Connector
+	}
+
+	type args struct {
+		ctx             context.Context
+		customContentID int
+		format          string
+		versionID       int
+	}
+
+	testCases := []struct {
+		name    string
+		fields  fields
+		args    args
+		on      func(*fields)
+		wantErr bool
+		Err     error
+	}{
+		{
+			name: "when the parameters are correct",
+			args: args{
+				ctx:             context.TODO(),
+				customContentID: 10001,
+				format:          "anonymous_export_view",
+				versionID:       2,
+			},
+			on: func(fields *fields) {
+
+				client := mocks.NewConnector(t)
+
+				client.On("NewRequest",
+					context.Background(),
+					http.MethodGet,
+					"wiki/api/v2/custom-content/10001?body-format=anonymous_export_view&version=2",
+					"", nil).
+					Return(&http.Request{}, nil)
+
+				client.On("Call",
+					&http.Request{},
+					&model.CustomContentScheme{}).
+					Return(&model.ResponseScheme{}, nil)
+
+				fields.c = client
+
+			},
+		},
+
+		{
+			name: "when the http request cannot be created",
+			args: args{
+				ctx:             context.TODO(),
+				customContentID: 10001,
+				format:          "anonymous_export_view",
+				versionID:       2,
+			},
+			on: func(fields *fields) {
+
+				client := mocks.NewConnector(t)
+
+				client.On("NewRequest",
+					context.Background(),
+					http.MethodGet,
+					"wiki/api/v2/custom-content/10001?body-format=anonymous_export_view&version=2",
+					"", nil).
+					Return(&http.Request{}, errors.New("error, unable to create the http request"))
+
+				fields.c = client
+			},
+			wantErr: true,
+			Err:     errors.New("error, unable to create the http request"),
+		},
+
+		{
+			name: "when the custom content id is not provided",
+			args: args{
+				ctx: context.TODO(),
+			},
+			wantErr: true,
+			Err:     model.ErrNoCustomContentIDError,
+		},
+	}
+
+	for _, testCase := range testCases {
+		t.Run(testCase.name, func(t *testing.T) {
+
+			if testCase.on != nil {
+				testCase.on(&testCase.fields)
+			}
+
+			newService := NewCustomContentService(testCase.fields.c)
+
+			gotResult, gotResponse, err := newService.Get(
+				testCase.args.ctx,
+				testCase.args.customContentID,
+				testCase.args.format,
+				testCase.args.versionID,
+			)
+
+			if testCase.wantErr {
+
+				if err != nil {
+					t.Logf("error returned: %v", err.Error())
+				}
+
+				assert.EqualError(t, err, testCase.Err.Error())
+
+			} else {
+
+				assert.NoError(t, err)
+				assert.NotEqual(t, gotResponse, nil)
+				assert.NotEqual(t, gotResult, nil)
+			}
+
+		})
+	}
+}
diff --git a/confluence/internal/space_v2_impl_test.go b/confluence/internal/space_v2_impl_test.go
index f99af06..51484dd 100644
--- a/confluence/internal/space_v2_impl_test.go
+++ b/confluence/internal/space_v2_impl_test.go
@@ -242,3 +242,111 @@ func Test_internalSpaceV2Impl_Get(t *testing.T) {
 		})
 	}
 }
+
+func Test_internalSpaceV2Impl_Permissions(t *testing.T) {
+
+	type fields struct {
+		c service.Connector
+	}
+
+	type args struct {
+		ctx     context.Context
+		spaceID int
+		cursor  string
+		limit   int
+	}
+
+	testCases := []struct {
+		name    string
+		fields  fields
+		args    args
+		on      func(*fields)
+		wantErr bool
+		Err     error
+	}{
+		{
+			name: "when the parameters are correct",
+			args: args{
+				ctx:     context.TODO(),
+				spaceID: 10001,
+				cursor:  "cursor_sample_uuid",
+				limit:   50,
+			},
+			on: func(fields *fields) {
+
+				client := mocks.NewConnector(t)
+
+				client.On("NewRequest",
+					context.Background(),
+					http.MethodGet,
+					"wiki/api/v2/spaces/10001/permissions?cursor=cursor_sample_uuid&limit=50",
+					"", nil).
+					Return(&http.Request{}, nil)
+
+				client.On("Call",
+					&http.Request{},
+					&model.SpacePermissionPageScheme{}).
+					Return(&model.ResponseScheme{}, nil)
+
+				fields.c = client
+
+			},
+		},
+
+		{
+			name: "when the http request cannot be created",
+			args: args{
+				ctx:     context.TODO(),
+				spaceID: 10001,
+				cursor:  "cursor_sample_uuid",
+				limit:   50,
+			},
+			on: func(fields *fields) {
+
+				client := mocks.NewConnector(t)
+
+				client.On("NewRequest",
+					context.Background(),
+					http.MethodGet,
+					"wiki/api/v2/spaces/10001/permissions?cursor=cursor_sample_uuid&limit=50",
+					"", nil).
+					Return(&http.Request{}, errors.New("error, unable to create the http request"))
+
+				fields.c = client
+
+			},
+			wantErr: true,
+			Err:     errors.New("error, unable to create the http request"),
+		},
+	}
+
+	for _, testCase := range testCases {
+		t.Run(testCase.name, func(t *testing.T) {
+
+			if testCase.on != nil {
+				testCase.on(&testCase.fields)
+			}
+
+			newService := NewSpaceV2Service(testCase.fields.c)
+
+			gotResult, gotResponse, err := newService.Permissions(testCase.args.ctx, testCase.args.spaceID, testCase.args.cursor,
+				testCase.args.limit)
+
+			if testCase.wantErr {
+
+				if err != nil {
+					t.Logf("error returned: %v", err.Error())
+				}
+
+				assert.EqualError(t, err, testCase.Err.Error())
+
+			} else {
+
+				assert.NoError(t, err)
+				assert.NotEqual(t, gotResponse, nil)
+				assert.NotEqual(t, gotResult, nil)
+			}
+
+		})
+	}
+}