Repository: ctreminiom/go-atlassian
Pull request #320 (2024-09-18 08:10:39)

Added confluence template api
This pull request adds support for template operations in the Confluence API client. The main changes include the addition of a new `TemplateService` with methods to create, update, and retrieve templates, as well as the necessary data models and tests.

### New Template Service:
* Added `TemplateService` to the `Client` struct in `confluence/api_client_impl.go` to support template operations. ([`[1]`](diffhunk://#diff-3abcc00e0a4fec89ffa6f35078e3328df7e74bdf898e02f107e2abd584ae1c7fR63), [`[2]`](diffhunk://#diff-3abcc00e0a4fec89ffa6f35078e3328df7e74bdf898e02f107e2abd584ae1c7fR78))
* Implemented `TemplateService` with methods `Create`, `Update`, and `Get` in `confluence/internal/template_impl.go`. ([`confluence/internal/template_impl.goR1-R110`](diffhunk://#diff-e8c8e7d57edc08f164fc150bff98eb2e2e50a15db1cdb7f212d3fbf3aa0e6940R1-R110))

### Data Models:
* Introduced new data models for template operations in `pkg/infra/models/confluence_template.go`, including `CreateTemplateScheme`, `UpdateTemplateScheme`, and `ContentTemplateScheme`. ([`pkg/infra/models/confluence_template.goR1-R109`](diffhunk://#diff-0aab48a1e7ba83945a5f5047804b2c1dc60e95b9a2450a10c84bf72dbf8e7979R1-R109))

### Interface:
* Defined the `TemplateConnector` interface in `service/confluence/template.go` to abstract the template operations. ([`service/confluence/template.goR1-R30`](diffhunk://#diff-f64e71bcf8a2ffc683b96888d70a5aabe237f9418002d55acf06bc607c66c187R1-R30))

### Tests:
* Added unit tests for `TemplateService` methods in `confluence/internal/template_impl_test.go` to ensure proper functionality. ([`confluence/internal/template_impl_test.goR1-R380`](diffhunk://#diff-cb60585adda840cc5bb58ab14f2ed7791c266928696adc379e295ee47d271d5dR1-R380))


This is a partial implementation, I only implemented the function I currently use.

ctreminiom: Hi @Fank, sorry for the delayed response. I just tested and it works, thanks!!.


I created the official documentation: https://docs.go-atlassian.io/confluence-cloud/template. Can you please link the official documentation on the server/implementation method comments?. 
Fank: Will add it
Fank: @ctreminiom sorry for delay, updated URLs and updated code to latest master.

Patch:
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index e5e453e..48bc5fb 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -23,7 +23,7 @@ on:
 jobs:
   analyze:
     name: Analyze
-    runs-on: ubuntu-20.04
+    runs-on: ubuntu-latest
 
     strategy:
       fail-fast: false
diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml
index 7d1cd82..8b077da 100644
--- a/.github/workflows/coverage.yml
+++ b/.github/workflows/coverage.yml
@@ -18,7 +18,7 @@ jobs:
       - name: Set up Go
         uses: actions/setup-go@v5
         with:
-          go-version: 1.17
+          go-version-file: ./go.mod
 
       - name: Run coverage
         run: go test -race -coverprofile=coverage.out -covermode=atomic ./...
diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml
index e772904..4f6bf30 100644
--- a/.github/workflows/dependency-review.yml
+++ b/.github/workflows/dependency-review.yml
@@ -5,7 +5,8 @@
 # Source repository: https://github.com/actions/dependency-review-action
 # Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement
 name: 'Dependency Review'
-on: [pull_request]
+on:
+  pull_request:
 
 permissions:
   contents: read
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 00bc961..e58450d 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -19,8 +19,8 @@ jobs:
     strategy:
       max-parallel: 6
       matrix:
-        go: [1.22, 1.23]
-        os: [ubuntu-latest, macos-latest, windows-latest]
+        go: [ 1.23, 1.24]
+        os: [ ubuntu-latest, macos-latest, windows-latest ]
     runs-on: ${{ matrix.os }}
     steps:
       - name: Setup Go
@@ -32,7 +32,7 @@ jobs:
         uses: actions/checkout@v4
 
       - name: golangci-lint
-        uses: golangci/golangci-lint-action@v6
+        uses: golangci/golangci-lint-action@v7
         with:
           version: latest
           args: --timeout=10m
diff --git a/.golangci.yaml b/.golangci.yaml
index 8cb106b..23c895e 100644
--- a/.golangci.yaml
+++ b/.golangci.yaml
@@ -1,24 +1,37 @@
+version: "2"
 run:
   concurrency: 4
-  timeout: 20m
-  tests: false
-  # This gives us the ability to efficiently skip whole files by using "//go:build !codeanalysis" at the top of a file.
   build-tags:
     - codeanalysis
-
+  tests: false
 linters:
   enable:
     - revive
-
-linters-settings:
-  revive:
-    rules:
-      - name: unused-parameter
-        disabled: true # forces all unused to be "_" which leads to confusion
-
+  settings:
+    revive:
+      rules:
+        - name: unused-parameter
+          disabled: true
+  exclusions:
+    generated: lax
+    presets:
+      - comments
+      - common-false-positives
+      - legacy
+      - std-error-handling
+    paths:
+      - third_party$
+      - builtin$
+      - examples$
 issues:
   max-issues-per-linter: 0
   max-same-issues: 0
-
 severity:
-  default-severity: critical
+  default: critical
+formatters:
+  exclusions:
+    generated: lax
+    paths:
+      - third_party$
+      - builtin$
+      - examples$
diff --git a/README.md b/README.md
index 61db7fd..eeedd49 100644
--- a/README.md
+++ b/README.md
@@ -48,17 +48,17 @@ go get github.com/ctreminiom/go-atlassian/v2
 ## 📪 Packages
 Then import the package into your project as you normally would. You can import the following packages:
 
-| Module                  	            | Path                                               	     | URL's                                                                                	 |
-|--------------------------------------|----------------------------------------------------------|----------------------------------------------------------------------------------------|
-| Jira v2                 	            | `github.com/ctreminiom/go-atlassian/jira/v2`       	     | [Getting Started](https://docs.go-atlassian.io/jira-software-cloud/introduction)     	 |
-| Jira v3                 	            | `github.com/ctreminiom/go-atlassian/jira/v3`       	     | [Getting Started](https://docs.go-atlassian.io/jira-software-cloud/introduction)     	 |
-| Jira Software Agile     	            | `github.com/ctreminiom/go-atlassian/jira/agile`    	     | [Getting Started](https://docs.go-atlassian.io/jira-agile/introduction)              	 |
-| Jira Service Management 	            | `github.com/ctreminiom/go-atlassian/jira/sm`       	     | [Getting Started](https://docs.go-atlassian.io/jira-service-management/introduction) 	 |
-| Jira Assets             	            | `github.com/ctreminiom/go-atlassian/assets`        	     | [Getting Started](https://docs.go-atlassian.io/jira-assets/overview)                 	 |
-| Confluence              	            | `github.com/ctreminiom/go-atlassian/confluence`    	     | [Getting Started](https://docs.go-atlassian.io/confluence-cloud/introduction)        	 |
-| Confluence v2           	            | `github.com/ctreminiom/go-atlassian/confluence/v2` 	     | [Getting Started](https://docs.go-atlassian.io/confluence-cloud/v2/introduction)     	 |
-| Admin Cloud             	            | `github.com/ctreminiom/go-atlassian/admin`         	     | [Getting Started](https://docs.go-atlassian.io/atlassian-admin-cloud/overview)       	 |
-| Bitbucket Cloud *(In Progress)*<br/> | `github.com/ctreminiom/go-atlassian/bitbucket`         	 | [Getting Started](https://docs.go-atlassian.io/bitbucket-cloud/introduction)       	   |
+| Module                  	            | Path                                               	        | URL's                                                                                	 |
+|--------------------------------------|-------------------------------------------------------------|----------------------------------------------------------------------------------------|
+| Jira v2                 	            | `github.com/ctreminiom/go-atlassian/v2/jira/v2`       	     | [Getting Started](https://docs.go-atlassian.io/jira-software-cloud/introduction)     	 |
+| Jira v3                 	            | `github.com/ctreminiom/go-atlassian/v2/jira/v3`       	     | [Getting Started](https://docs.go-atlassian.io/jira-software-cloud/introduction)     	 |
+| Jira Software Agile     	            | `github.com/ctreminiom/go-atlassian/v2/jira/agile`    	     | [Getting Started](https://docs.go-atlassian.io/jira-agile/introduction)              	 |
+| Jira Service Management 	            | `github.com/ctreminiom/go-atlassian/v2/jira/sm`       	     | [Getting Started](https://docs.go-atlassian.io/jira-service-management/introduction) 	 |
+| Jira Assets             	            | `github.com/ctreminiom/go-atlassian/v2/assets`        	     | [Getting Started](https://docs.go-atlassian.io/jira-assets/overview)                 	 |
+| Confluence              	            | `github.com/ctreminiom/go-atlassian/v2/confluence`    	     | [Getting Started](https://docs.go-atlassian.io/confluence-cloud/introduction)        	 |
+| Confluence v2           	            | `github.com/ctreminiom/go-atlassian/v2/confluence/v2` 	     | [Getting Started](https://docs.go-atlassian.io/confluence-cloud/v2/introduction)     	 |
+| Admin Cloud             	            | `github.com/ctreminiom/go-atlassian/v2/admin`         	     | [Getting Started](https://docs.go-atlassian.io/atlassian-admin-cloud/overview)       	 |
+| Bitbucket Cloud *(In Progress)*<br/> | `github.com/ctreminiom/go-atlassian/v2/bitbucket`         	 | [Getting Started](https://docs.go-atlassian.io/bitbucket-cloud/introduction)       	   |
 
 -------------------------
 ## 🔨 Usage
@@ -173,7 +173,7 @@ package main
 import (  
     "context"  
     "fmt" 
-    "github.com/ctreminiom/go-atlassian/jira/v3" 
+    "github.com/ctreminiom/go-atlassian/v2/jira/v3" 
     "log" 
     "net/http" 
     "os"
diff --git a/confluence/api_client_impl.go b/confluence/api_client_impl.go
index ac6cd93..a2fb413 100644
--- a/confluence/api_client_impl.go
+++ b/confluence/api_client_impl.go
@@ -60,6 +60,7 @@ func New(httpClient common.HTTPClient, site string) (*Client, error) {
 	client.Search = internal.NewSearchService(client)
 	client.LongTask = internal.NewTaskService(client)
 	client.Analytics = internal.NewAnalyticsService(client)
+	client.Template = internal.NewTemplateService(client)
 
 	return client, nil
 }
@@ -74,6 +75,7 @@ type Client struct {
 	Search    *internal.SearchService
 	LongTask  *internal.TaskService
 	Analytics *internal.AnalyticsService
+	Template  *internal.TemplateService
 }
 
 func (c *Client) NewRequest(ctx context.Context, method, urlStr, contentType string, body interface{}) (*http.Request, error) {
diff --git a/confluence/internal/template_impl.go b/confluence/internal/template_impl.go
new file mode 100644
index 0000000..0b04864
--- /dev/null
+++ b/confluence/internal/template_impl.go
@@ -0,0 +1,110 @@
+package internal
+
+import (
+	"context"
+	"net/http"
+
+	"github.com/ctreminiom/go-atlassian/v2/pkg/infra/models"
+	"github.com/ctreminiom/go-atlassian/v2/service"
+	"github.com/ctreminiom/go-atlassian/v2/service/confluence"
+)
+
+// NewTemplateService creates a new instance of TemplateService.
+// It takes a service.Connector as inputs and returns a pointer to TemplateService.
+func NewTemplateService(client service.Connector) *TemplateService {
+	return &TemplateService{
+		internalClient: &internalTemplateImpl{c: client},
+	}
+}
+
+// TemplateService provides methods to interact with template operations in Confluence.
+type TemplateService struct {
+	// internalClient is the connector interface for content operations.
+	internalClient confluence.TemplateConnector
+}
+
+// Create creates a new template.
+//
+// POST /wiki/rest/api/template
+//
+// https://docs.go-atlassian.io/confluence-cloud/template#create-content-template
+func (t *TemplateService) Create(ctx context.Context, payload *models.CreateTemplateScheme) (*models.ContentTemplateScheme, *models.ResponseScheme, error) {
+	return t.internalClient.Create(ctx, payload)
+}
+
+// Update updates a template.
+//
+// PUT /wiki/rest/api/template
+//
+// https://docs.go-atlassian.io/confluence-cloud/template#update-content-template
+func (t *TemplateService) Update(ctx context.Context, payload *models.UpdateTemplateScheme) (*models.ContentTemplateScheme, *models.ResponseScheme, error) {
+	return t.internalClient.Update(ctx, payload)
+}
+
+// Get content template by ID.
+//
+// GET /wiki/rest/api/template/{id}
+//
+// https://docs.go-atlassian.io/confluence-cloud/template#get-content-template
+func (t *TemplateService) Get(ctx context.Context, templateID string) (*models.ContentTemplateScheme, *models.ResponseScheme, error) {
+	return t.internalClient.Get(ctx, templateID)
+}
+
+// internalTemplateImpl is the internal implementation of TemplateService.
+type internalTemplateImpl struct {
+	c service.Connector
+}
+
+// Create implements TemplateService.Create.
+func (i *internalTemplateImpl) Create(ctx context.Context, payload *models.CreateTemplateScheme) (*models.ContentTemplateScheme, *models.ResponseScheme, error) {
+	endpoint := "/wiki/rest/api/template"
+
+	request, err := i.c.NewRequest(ctx, http.MethodPost, endpoint, "", payload)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	result := new(models.ContentTemplateScheme)
+	response, err := i.c.Call(request, result)
+	if err != nil {
+		return nil, response, err
+	}
+
+	return result, response, nil
+}
+
+// Update implements TemplateService.Update.
+func (i *internalTemplateImpl) Update(ctx context.Context, payload *models.UpdateTemplateScheme) (*models.ContentTemplateScheme, *models.ResponseScheme, error) {
+	endpoint := "/wiki/rest/api/template"
+
+	request, err := i.c.NewRequest(ctx, http.MethodPut, endpoint, "", payload)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	result := new(models.ContentTemplateScheme)
+	response, err := i.c.Call(request, result)
+	if err != nil {
+		return nil, response, err
+	}
+
+	return result, response, nil
+}
+
+// Get implements TemplateService.Get.
+func (i *internalTemplateImpl) Get(ctx context.Context, templateID string) (*models.ContentTemplateScheme, *models.ResponseScheme, error) {
+	endpoint := "/wiki/rest/api/template/" + templateID
+
+	request, err := i.c.NewRequest(ctx, http.MethodGet, endpoint, "", nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	result := new(models.ContentTemplateScheme)
+	response, err := i.c.Call(request, result)
+	if err != nil {
+		return nil, response, err
+	}
+
+	return result, response, nil
+}
diff --git a/confluence/v2/api_client_impl.go b/confluence/v2/api_client_impl.go
index c2f78e4..ce84d37 100644
--- a/confluence/v2/api_client_impl.go
+++ b/confluence/v2/api_client_impl.go
@@ -1,4 +1,4 @@
-package confluence
+package v2
 
 import (
 	"bytes"
diff --git a/go.mod b/go.mod
index 9a15f1e..e7f1376 100644
--- a/go.mod
+++ b/go.mod
@@ -6,7 +6,7 @@ require (
 	dario.cat/mergo v1.0.1
 	github.com/google/go-querystring v1.1.0
 	github.com/google/uuid v1.6.0
-	github.com/stretchr/testify v1.9.0
+	github.com/stretchr/testify v1.10.0
 	github.com/tidwall/gjson v1.18.0
 )
 
diff --git a/go.sum b/go.sum
index 30848f5..c277568 100644
--- a/go.sum
+++ b/go.sum
@@ -12,8 +12,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
 github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
-github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
-github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
+github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
 github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
 github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
diff --git a/jira/internal/issue_archive_impl.go b/jira/internal/issue_archive_impl.go
new file mode 100644
index 0000000..f8d7d1b
--- /dev/null
+++ b/jira/internal/issue_archive_impl.go
@@ -0,0 +1,200 @@
+// Package internal jira/internal/issue_archive_impl.go
+package internal
+
+import (
+	"context"
+	"fmt"
+	model "github.com/ctreminiom/go-atlassian/v2/pkg/infra/models"
+	"github.com/ctreminiom/go-atlassian/v2/service"
+	"github.com/ctreminiom/go-atlassian/v2/service/jira"
+	"net/http"
+	"path"
+)
+
+// NewIssueArchivalService creates a new instance of IssueArchivalService.
+// It initializes an internal client with the given service connector and API version,
+// which will be used to perform issue archival operations.
+//
+// Parameters:
+//   - client: The service connector used to communicate with the underlying API.
+//   - version: The API version to be used by the archival service.
+//
+// Returns:
+//   - A pointer to an IssueArchivalService configured with the provided client and version.
+//
+// Example usage:
+//
+//	client := myConnectorInstance // your implementation of service.Connector
+//	version := "v3"
+//	archiveService := NewIssueArchivalService(client, version)
+func NewIssueArchivalService(client service.Connector, version string) *IssueArchivalService {
+	return &IssueArchivalService{
+		internalClient: &internalIssueArchivalImpl{c: client, version: version},
+	}
+}
+
+// IssueArchivalService provides methods to manage issue archival operations, including preserving, restoring, and exporting archived issues.
+type IssueArchivalService struct {
+	internalClient jira.ArchiveService
+}
+
+// Preserve archives the given issues based on their issue IDs or keys.
+//
+// Parameters:
+//   - ctx: The context for controlling request lifecycle and deadlines.
+//   - issueIdsOrKeys: A list of issue IDs or keys to be archived.
+//
+// Returns:
+//   - result: A structure containing details of the archival synchronization process.
+//   - response: The HTTP response scheme for the request.
+//   - err: An error if the operation fails.
+//
+// https://docs.go-atlassian.io/jira-software-cloud/issues/archiving#archive-issues-by-issue-id-key
+func (i *IssueArchivalService) Preserve(ctx context.Context, issueIDsOrKeys []string) (*model.IssueArchivalSyncResponseScheme, *model.ResponseScheme, error) {
+	return i.internalClient.Preserve(ctx, issueIDsOrKeys)
+}
+
+// PreserveByJQL archives issues that match the provided JQL query.
+//
+// Parameters:
+//   - ctx: The context for request lifecycle management.
+//   - jql: The JQL query to select issues for archival.
+//
+// Returns:
+//   - taskID: A unique identifier for the asynchronous archival task.
+//   - response: The HTTP response scheme for the request.
+//   - err: An error if the operation fails.
+//
+// https://docs.go-atlassian.io/jira-software-cloud/issues/archiving#archive-issues-by-jql
+func (i *IssueArchivalService) PreserveByJQL(ctx context.Context, jql string) (string, *model.ResponseScheme, error) {
+	return i.internalClient.PreserveByJQL(ctx, jql)
+}
+
+// Restore brings back the given archived issues using their issue IDs or keys.
+//
+// Parameters:
+//   - ctx: The context for controlling request execution.
+//   - issueIdsOrKeys: A list of issue IDs or keys to be restored from the archive.
+//
+// Returns:
+//   - result: A structure containing details of the restoration process.
+//   - response: The HTTP response scheme for the request.
+//   - err: An error if the operation fails.
+//
+// https://docs.go-atlassian.io/jira-software-cloud/issues/archiving#restore-issues-by-issue-id-key
+func (i *IssueArchivalService) Restore(ctx context.Context, issueIDsOrKeys []string) (*model.IssueArchivalSyncResponseScheme, *model.ResponseScheme, error) {
+	return i.internalClient.Restore(ctx, issueIDsOrKeys)
+}
+
+// Export generates an export of archived issues based on the provided payload.
+//
+// Parameters:
+//   - ctx: The context for controlling request execution.
+//   - payload: The export configuration, including filters and format specifications.
+//
+// Returns:
+//   - taskID: A unique identifier for the asynchronous export task.
+//   - response: The HTTP response scheme for the request.
+//   - err: An error if the operation fails.
+//
+// https://docs.go-atlassian.io/jira-software-cloud/issues/archiving#export-archived-issues
+func (i *IssueArchivalService) Export(ctx context.Context, payload *model.IssueArchivalExportPayloadScheme) (*model.IssueArchiveExportResultScheme, *model.ResponseScheme, error) {
+	return i.internalClient.Export(ctx, payload)
+}
+
+type internalIssueArchivalImpl struct {
+	c       service.Connector
+	version string
+}
+
+func (i *internalIssueArchivalImpl) Preserve(ctx context.Context, issueIDsOrKeys []string) (result *model.IssueArchivalSyncResponseScheme, response *model.ResponseScheme, err error) {
+
+	if len(issueIDsOrKeys) == 0 {
+		return nil, nil, model.ErrNoIssuesSlice
+	}
+
+	payload := make(map[string]interface{})
+	payload["issueIdsOrKeys"] = issueIDsOrKeys
+
+	endpoint := fmt.Sprintf("rest/api/%s/issue/archive", i.version)
+
+	request, err := i.c.NewRequest(ctx, http.MethodPut, endpoint, "", payload)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	report := new(model.IssueArchivalSyncResponseScheme)
+	response, err = i.c.Call(request, report)
+	if err != nil {
+		return nil, response, err
+	}
+
+	return report, response, nil
+}
+
+func (i *internalIssueArchivalImpl) PreserveByJQL(ctx context.Context, jql string) (taskID string, response *model.ResponseScheme, err error) {
+
+	if jql == "" {
+		return "", nil, model.ErrNoJQL
+	}
+
+	payload := make(map[string]interface{})
+	payload["jql"] = jql
+
+	endpoint := fmt.Sprintf("rest/api/%s/issue/archive", i.version)
+
+	request, err := i.c.NewRequest(ctx, http.MethodPost, endpoint, "", payload)
+	if err != nil {
+		return "", nil, err
+	}
+
+	response, err = i.c.Call(request, nil)
+	if err != nil {
+		return "", response, err
+	}
+
+	return path.Base(response.Bytes.String()), response, nil
+}
+
+func (i *internalIssueArchivalImpl) Restore(ctx context.Context, issueIDsOrKeys []string) (result *model.IssueArchivalSyncResponseScheme, response *model.ResponseScheme, err error) {
+
+	if len(issueIDsOrKeys) == 0 {
+		return nil, nil, model.ErrNoIssuesSlice
+	}
+
+	payload := make(map[string]interface{})
+	payload["issueIdsOrKeys"] = issueIDsOrKeys
+
+	endpoint := fmt.Sprintf("rest/api/%s/issue/unarchive", i.version)
+
+	request, err := i.c.NewRequest(ctx, http.MethodPut, endpoint, "", payload)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	report := new(model.IssueArchivalSyncResponseScheme)
+	response, err = i.c.Call(request, report)
+	if err != nil {
+		return nil, response, err
+	}
+
+	return report, response, nil
+}
+
+func (i *internalIssueArchivalImpl) Export(ctx context.Context, payload *model.IssueArchivalExportPayloadScheme) (task *model.IssueArchiveExportResultScheme, response *model.ResponseScheme, err error) {
+
+	endpoint := fmt.Sprintf("rest/api/%s/issues/archive/export", i.version)
+
+	request, err := i.c.NewRequest(ctx, http.MethodPut, endpoint, "", payload)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	result := new(model.IssueArchiveExportResultScheme)
+	response, err = i.c.Call(request, result)
+	if err != nil {
+		return nil, response, err
+	}
+
+	return result, response, nil
+}
diff --git a/jira/internal/watcher_impl.go b/jira/internal/watcher_impl.go
index fda1780..bbaf98d 100644
--- a/jira/internal/watcher_impl.go
+++ b/jira/internal/watcher_impl.go
@@ -93,9 +93,9 @@ func (i *internalWatcherImpl) Add(ctx context.Context, issueKeyOrID string, acco
 
 	endpoint := fmt.Sprintf("rest/api/%v/issue/%v/watchers", i.version, issueKeyOrID)
 
-	var payload []byte // add self user
+	var payload string // add self user
 	if len(accountID) > 0 {
-		payload = []byte(accountID[0]) // add another user
+		payload = accountID[0] // add another user
 	}
 	request, err := i.c.NewRequest(ctx, http.MethodPost, endpoint, "", payload)
 	if err != nil {
diff --git a/jira/v2/api_client_impl.go b/jira/v2/api_client_impl.go
index 0b4ed10..28a688f 100644
--- a/jira/v2/api_client_impl.go
+++ b/jira/v2/api_client_impl.go
@@ -397,6 +397,8 @@ func New(httpClient common.HTTPClient, site string) (*Client, error) {
 	client.NotificationScheme = projectNotificationScheme
 	client.Team = internal.NewTeamService(client)
 
+	client.Archive = internal.NewIssueArchivalService(client, APIVersion)
+
 	return client, nil
 }
 
@@ -423,6 +425,8 @@ type Client struct {
 	JQL                *internal.JQLService
 	NotificationScheme *internal.NotificationSchemeService
 	Team               *internal.TeamService
+
+	Archive *internal.IssueArchivalService
 }
 
 // NewRequest creates an API request.
diff --git a/jira/v3/api_client_impl.go b/jira/v3/api_client_impl.go
index e29db9e..30cb60e 100644
--- a/jira/v3/api_client_impl.go
+++ b/jira/v3/api_client_impl.go
@@ -397,6 +397,8 @@ func New(httpClient common.HTTPClient, site string) (*Client, error) {
 	client.NotificationScheme = projectNotificationScheme
 	client.Team = internal.NewTeamService(client)
 
+	client.Archival = internal.NewIssueArchivalService(client, APIVersion)
+
 	return client, nil
 }
 
@@ -423,6 +425,8 @@ type Client struct {
 	JQL                *internal.JQLService
 	NotificationScheme *internal.NotificationSchemeService
 	Team               *internal.TeamService
+
+	Archival *internal.IssueArchivalService
 }
 
 // NewRequest creates an API request.
diff --git a/pkg/infra/models/confluence_content_restriction.go b/pkg/infra/models/confluence_content_restriction.go
index b2de17e..64322d9 100644
--- a/pkg/infra/models/confluence_content_restriction.go
+++ b/pkg/infra/models/confluence_content_restriction.go
@@ -36,15 +36,15 @@ type ContentRestrictionUpdatePayloadScheme struct {
 
 // ContentRestrictionUpdateScheme represents a content restriction to be updated in Confluence.
 type ContentRestrictionUpdateScheme struct {
-	Operation    string                                     `json:"operation,omitempty"`    // The operation of the restriction.
-	Restrictions *ContentRestrictionRestrictionUpdateScheme `json:"restrictions,omitempty"` // The details of the restriction to be updated.
-	Content      *ContentScheme                             `json:"content,omitempty"`      // The content of the restriction to be updated.
+	Operation    string                                     `json:"operation,omitempty"` // The operation of the restriction.
+	Restrictions *ContentRestrictionRestrictionUpdateScheme `json:"restrictions"`        // The details of the restriction to be updated.
+	Content      *ContentScheme                             `json:"content,omitempty"`   // The content of the restriction to be updated.
 }
 
 // ContentRestrictionRestrictionUpdateScheme represents the details of a content restriction to be updated in Confluence.
 type ContentRestrictionRestrictionUpdateScheme struct {
-	Group []*SpaceGroupScheme  `json:"group,omitempty"` // The group permissions of the restriction to be updated.
-	User  []*ContentUserScheme `json:"user,omitempty"`  // The user permissions of the restriction to be updated.
+	Group []*SpaceGroupScheme  `json:"group"` // The group permissions of the restriction to be updated.
+	User  []*ContentUserScheme `json:"user"`  // The user permissions of the restriction to be updated.
 }
 
 // ContentRestrictionByOperationScheme represents a content restriction by operation in Confluence.
diff --git a/pkg/infra/models/confluence_template.go b/pkg/infra/models/confluence_template.go
new file mode 100644
index 0000000..1124322
--- /dev/null
+++ b/pkg/infra/models/confluence_template.go
@@ -0,0 +1,112 @@
+package models
+
+// UpdateTemplateScheme represents a Confluence template update.
+type UpdateTemplateScheme struct {
+	// TemplateID the ID of the template being updated.
+	TemplateID string `json:"templateId"`
+	// Name of the template. Set to the current name if this field is not being updated.
+	Name string `json:"name"`
+	// TemplateType of the template.
+	TemplateType string `json:"templateType"`
+	// Body of the new content.
+	Body *ContentTemplateBodyCreateScheme `json:"body"`
+	// Description of the template.
+	// Max length: 100
+	Description string `json:"description"`
+	// Labels to add to the template.
+	Labels []LabelValueScheme `json:"labels,omitempty"`
+	// Space the template is in.
+	// Only the Space.Key is required.
+	Space *SpaceScheme `json:"space"`
+}
+
+// CreateTemplateScheme represents a Confluence template creation.
+type CreateTemplateScheme struct {
+	// Name of the template. Set to the current name if this field is not being updated.
+	Name string `json:"name"`
+	// TemplateType of the template.
+	TemplateType string `json:"templateType"`
+	// Body of the new content.
+	Body *ContentTemplateBodyCreateScheme `json:"body"`
+	// Description of the template.
+	// Max length: 255
+	Description string `json:"description"`
+	// Labels to add to the template.
+	Labels []LabelValueScheme `json:"labels,omitempty"`
+	// Space the template is in.
+	// Only the Space.Key is required.
+	Space *SpaceScheme `json:"space"`
+}
+
+// ContentTemplateBodySchema is the body of the template.
+type ContentTemplateBodySchema struct {
+	View                *ContentBodyCreateScheme `json:"view,omitempty"`
+	ExportView          *ContentBodyCreateScheme `json:"export_view,omitempty"`
+	StyledView          *ContentBodyCreateScheme `json:"styled_view,omitempty"`
+	Storage             *ContentBodyCreateScheme `json:"storage,omitempty"`
+	Editor              *ContentBodyCreateScheme `json:"editor,omitempty"`
+	Editor2             *ContentBodyCreateScheme `json:"editor2,omitempty"`
+	Wiki                *ContentBodyCreateScheme `json:"wiki,omitempty"`
+	AtlasDocFormat      *ContentBodyCreateScheme `json:"atlas_doc_format,omitempty"`
+	AnonymousExportView *ContentBodyCreateScheme `json:"anonymous_export_view,omitempty"`
+}
+
+// ContentBodyScheme is used when creating or updating content.
+type ContentBodyScheme struct {
+	// The body of the content in the relevant format.
+	Value string `json:"value"`
+	// The content format type. Set the value of this property to the name of the format being used, e.g. 'storage'.
+	// Valid values: view, export_view, styled_view, storage, editor, editor2, anonymous_export_view, wiki, atlas_doc_format, plain, raw
+	Representation string `json:"representation"`
+}
+
+// ContentTemplateBodyCreateScheme is the body of the template for creating.
+// Only one body format should be specified as the property for this object, e.g. storage.
+// Note, editor2 format is used by Atlassian only. anonymous_export_view is the same as export_view format but only content viewable by an anonymous user is included.
+type ContentTemplateBodyCreateScheme struct {
+	View                *ContentBodyCreateScheme `json:"view,omitempty"`
+	ExportView          *ContentBodyCreateScheme `json:"export_view,omitempty"`
+	StyledView          *ContentBodyCreateScheme `json:"styled_view,omitempty"`
+	Storage             *ContentBodyCreateScheme `json:"storage,omitempty"`
+	Editor              *ContentBodyCreateScheme `json:"editor,omitempty"`
+	Editor2             *ContentBodyCreateScheme `json:"editor2,omitempty"`
+	Wiki                *ContentBodyCreateScheme `json:"wiki,omitempty"`
+	AtlasDocFormat      *ContentBodyCreateScheme `json:"atlas_doc_format,omitempty"`
+	AnonymousExportView *ContentBodyCreateScheme `json:"anonymous_export_view,omitempty"`
+}
+
+// ContentBodyCreateScheme is used when creating or updating content.
+type ContentBodyCreateScheme struct {
+	// The body of the content in the relevant format.
+	Value string `json:"value"`
+	// The content format type. Set the value of this property to the name of the format being used, e.g. 'storage'.
+	// Valid values: view, export_view, styled_view, storage, editor, editor2, anonymous_export_view, wiki, atlas_doc_format, plain, raw
+	Representation string `json:"representation"`
+}
+
+// ContentTemplateScheme represents a Confluence template.
+type ContentTemplateScheme struct {
+	TemplateID           string                  `json:"templateId"`
+	OriginalTemplate     *OriginalTemplateScheme `json:"originalTemplate,omitempty"`
+	ReferencingBlueprint string                  `json:"referencingBlueprint"`
+	Name                 string                  `json:"name"`
+	Description          string                  `json:"description"`
+	// Labels to add to the template.
+	Labels []LabelValueScheme `json:"labels"`
+	// TemplateType of the template.
+	TemplateType  string                           `json:"templateType"`
+	EditorVersion string                           `json:"editorVersion"`
+	Body          *ContentTemplateBodySchema       `json:"body"`
+	Expandable    *ContentTemplateExpandableScheme `json:"_expandable,omitempty"`
+}
+
+// OriginalTemplateScheme contains the original template reference.
+type OriginalTemplateScheme struct {
+	PluginKey string `json:"pluginKey"`
+	ModuleKey string `json:"moduleKey"`
+}
+
+// ContentTemplateExpandableScheme represents the expandable properties of a template.
+type ContentTemplateExpandableScheme struct {
+	Body string `json:"body"`
+}
diff --git a/pkg/infra/models/issue_archival.go b/pkg/infra/models/issue_archival.go
new file mode 100644
index 0000000..60804bc
--- /dev/null
+++ b/pkg/infra/models/issue_archival.go
@@ -0,0 +1,48 @@
+// Package models provides the models for the issue archival service.
+package models
+
+// IssueArchivalSyncResponseScheme represents the response from the issue archival synchronization operation.
+type IssueArchivalSyncResponseScheme struct {
+	Errors                *IssueArchivalSyncErrorScheme `json:"errors"`
+	NumberOfIssuesUpdated int                           `json:"numberOfIssuesUpdated"`
+}
+
+// IssueArchivalSyncErrorScheme represents the error details for the issue archival synchronization operation.
+type IssueArchivalSyncErrorScheme struct {
+	IssueIsSubtask             *IssueArchivalErrorScheme `json:"issueIsSubtask"`
+	IssuesInArchivedProjects   *IssueArchivalErrorScheme `json:"issuesInArchivedProjects"`
+	IssuesInUnlicensedProjects *IssueArchivalErrorScheme `json:"issuesInUnlicensedProjects"`
+	IssuesNotFound             *IssueArchivalErrorScheme `json:"issuesNotFound"`
+	UserDoesNotHavePermission  *IssueArchivalErrorScheme `json:"userDoesNotHavePermission"`
+}
+
+// IssueArchivalErrorScheme represents the error details for the issue archival operation.
+type IssueArchivalErrorScheme struct {
+	Count          int      `json:"count"`
+	IssueIDsOrKeys []string `json:"issueIdsOrKeys"`
+	Message        string   `json:"message"`
+}
+
+// IssueArchivalExportPayloadScheme represents the payload for the issue archival export operation.
+type IssueArchivalExportPayloadScheme struct {
+	ArchivedBy        []string                      `json:"archivedBy"`
+	ArchivedDateRange *DateRangeFilterRequestScheme `json:"archivedDateRange,omitempty"`
+	IssueTypes        []string                      `json:"issueTypes"`
+	Projects          []string                      `json:"projects"`
+	Reporters         []string                      `json:"reporters"`
+}
+
+// DateRangeFilterRequestScheme represents the date range filter for the issue archival export operation.
+type DateRangeFilterRequestScheme struct {
+	DateAfter  string `json:"dateAfter,omitempty"`
+	DateBefore string `json:"dateBefore,omitempty"`
+}
+
+// IssueArchiveExportResultScheme represents the result of an issue archival export operation.
+type IssueArchiveExportResultScheme struct {
+	TaskID        string `json:"taskId,omitempty"`
+	Payload       string `json:"payload,omitempty"`
+	Progress      int    `json:"progress,omitempty"`
+	SubmittedTime int64  `json:"submittedTime,omitempty"`
+	Status        string `json:"status,omitempty"`
+}
diff --git a/service/confluence/template.go b/service/confluence/template.go
new file mode 100644
index 0000000..25bc50a
--- /dev/null
+++ b/service/confluence/template.go
@@ -0,0 +1,31 @@
+package confluence
+
+import (
+	"context"
+
+	"github.com/ctreminiom/go-atlassian/v2/pkg/infra/models"
+)
+
+// TemplateConnector provides methods to interact with template operations in Confluence.
+type TemplateConnector interface {
+	// Create creates a new template.
+	//
+	// POST /wiki/rest/api/template
+	//
+	// https://docs.go-atlassian.io/confluence-cloud/template#create-content-template
+	Create(ctx context.Context, payload *models.CreateTemplateScheme) (*models.ContentTemplateScheme, *models.ResponseScheme, error)
+
+	// Update updates a template.
+	//
+	// PUT /wiki/rest/api/template
+	//
+	// https://docs.go-atlassian.io/confluence-cloud/template#update-content-template
+	Update(ctx context.Context, payload *models.UpdateTemplateScheme) (*models.ContentTemplateScheme, *models.ResponseScheme, error)
+
+	// Get content template by ID.
+	//
+	// GET /wiki/rest/api/template/{id}
+	//
+	// https://docs.go-atlassian.io/confluence-cloud/template#get-content-template
+	Get(ctx context.Context, templateID string) (*models.ContentTemplateScheme, *models.ResponseScheme, error)
+}
diff --git a/service/jira/archive.go b/service/jira/archive.go
new file mode 100644
index 0000000..7df6422
--- /dev/null
+++ b/service/jira/archive.go
@@ -0,0 +1,67 @@
+// Package jira ...
+package jira
+
+import (
+	"context"
+	"github.com/ctreminiom/go-atlassian/v2/pkg/infra/models"
+)
+
+// ArchiveService provides methods to manage issue archival operations, including preserving, restoring, and exporting archived issues.
+type ArchiveService interface {
+
+	// Preserve archives the given issues based on their issue IDs or keys.
+	//
+	// Parameters:
+	//   - ctx: The context for controlling request lifecycle and deadlines.
+	//   - issueIdsOrKeys: A list of issue IDs or keys to be archived.
+	//
+	// Returns:
+	//   - result: A structure containing details of the archival synchronization process.
+	//   - response: The HTTP response scheme for the request.
+	//   - err: An error if the operation fails.
+	//
+	// https://docs.go-atlassian.io/jira-software-cloud/issues/archiving#archive-issues-by-issue-id-key
+	Preserve(ctx context.Context, issueIDsOrKeys []string) (result *models.IssueArchivalSyncResponseScheme, response *models.ResponseScheme, err error)
+
+	// PreserveByJQL archives issues that match the provided JQL query.
+	//
+	// Parameters:
+	//   - ctx: The context for request lifecycle management.
+	//   - jql: The JQL query to select issues for archival.
+	//
+	// Returns:
+	//   - taskID: A unique identifier for the asynchronous archival task.
+	//   - response: The HTTP response scheme for the request.
+	//   - err: An error if the operation fails.
+	//
+	// https://docs.go-atlassian.io/jira-software-cloud/issues/archiving#archive-issues-by-jql
+	PreserveByJQL(ctx context.Context, jql string) (taskID string, response *models.ResponseScheme, err error)
+
+	// Restore brings back the given archived issues using their issue IDs or keys.
+	//
+	// Parameters:
+	//   - ctx: The context for controlling request execution.
+	//   - issueIdsOrKeys: A list of issue IDs or keys to be restored from the archive.
+	//
+	// Returns:
+	//   - result: A structure containing details of the restoration process.
+	//   - response: The HTTP response scheme for the request.
+	//   - err: An error if the operation fails.
+	//
+	// https://docs.go-atlassian.io/jira-software-cloud/issues/archiving#restore-issues-by-issue-id-key
+	Restore(ctx context.Context, issueIDsOrKeys []string) (result *models.IssueArchivalSyncResponseScheme, response *models.ResponseScheme, err error)
+
+	// Export generates an export of archived issues based on the provided payload.
+	//
+	// Parameters:
+	//   - ctx: The context for controlling request execution.
+	//   - payload: The export configuration, including filters and format specifications.
+	//
+	// Returns:
+	//   - taskID: A unique identifier for the asynchronous export task.
+	//   - response: The HTTP response scheme for the request.
+	//   - err: An error if the operation fails.
+	//
+	// https://docs.go-atlassian.io/jira-software-cloud/issues/archiving#export-archived-issues
+	Export(ctx context.Context, payload *models.IssueArchivalExportPayloadScheme) (task *models.IssueArchiveExportResultScheme, response *models.ResponseScheme, err error)
+}


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 0eb33c6..4813ce0 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -13,8 +13,8 @@ jobs:
     strategy:
       max-parallel: 6
       matrix:
-        go-version: [1.22, 1.23]
-        platform: [ubuntu-latest, macos-latest, windows-latest]
+        go-version: [ 1.23, 1.24 ]
+        platform: [ ubuntu-latest, macos-latest, windows-latest ]
 
     runs-on: ${{ matrix.platform }}
 
diff --git a/confluence/internal/template_impl_test.go b/confluence/internal/template_impl_test.go
new file mode 100644
index 0000000..d523795
--- /dev/null
+++ b/confluence/internal/template_impl_test.go
@@ -0,0 +1,380 @@
+package internal
+
+import (
+	"context"
+	"errors"
+	"net/http"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+
+	"github.com/ctreminiom/go-atlassian/v2/pkg/infra/models"
+	"github.com/ctreminiom/go-atlassian/v2/service"
+	"github.com/ctreminiom/go-atlassian/v2/service/mocks"
+)
+
+func Test_internalTemplateImpl_Create(t *testing.T) {
+	type fields struct {
+		c service.Connector
+	}
+	type args struct {
+		ctx     context.Context
+		payload *models.CreateTemplateScheme
+	}
+	tests := []struct {
+		name    string
+		fields  fields
+		args    args
+		on      func(*fields)
+		wantErr bool
+		Err     error
+	}{
+		{
+			name: "CreateTemplateScheme.Create returned error",
+			args: args{
+				ctx: context.Background(),
+				payload: &models.CreateTemplateScheme{
+					Name:         "Test Template",
+					TemplateType: "page",
+					Body: &models.ContentTemplateBodyCreateScheme{
+						View: &models.ContentBodyCreateScheme{
+							Value:          "<h1>Test Template</h1>",
+							Representation: "storage",
+						},
+					},
+					Description: "This is a test template",
+					Space: &models.SpaceScheme{
+						Key: "TEST",
+					},
+				},
+			},
+			on: func(fields *fields) {
+				client := mocks.NewConnector(t)
+
+				client.On("NewRequest",
+					context.Background(),
+					http.MethodPost,
+					"/wiki/rest/api/template",
+					"",
+					&models.CreateTemplateScheme{
+						Name:         "Test Template",
+						TemplateType: "page",
+						Body: &models.ContentTemplateBodyCreateScheme{
+							View: &models.ContentBodyCreateScheme{
+								Value:          "<h1>Test Template</h1>",
+								Representation: "storage",
+							},
+						},
+						Description: "This is a test template",
+						Space: &models.SpaceScheme{
+							Key: "TEST",
+						},
+					},
+				).
+					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: "CreateTemplateScheme.Create success",
+			args: args{
+				ctx: context.Background(),
+				payload: &models.CreateTemplateScheme{
+					Name:         "Test Template",
+					TemplateType: "page",
+					Body: &models.ContentTemplateBodyCreateScheme{
+						View: &models.ContentBodyCreateScheme{
+							Value:          "<h1>Test Template</h1>",
+							Representation: "storage",
+						},
+					},
+					Description: "This is a test template",
+					Space: &models.SpaceScheme{
+						Key: "TEST",
+					},
+				},
+			},
+			on: func(fields *fields) {
+				client := mocks.NewConnector(t)
+
+				client.On("NewRequest",
+					context.Background(),
+					http.MethodPost,
+					"/wiki/rest/api/template",
+					"",
+					&models.CreateTemplateScheme{
+						Name:         "Test Template",
+						TemplateType: "page",
+						Body: &models.ContentTemplateBodyCreateScheme{
+							View: &models.ContentBodyCreateScheme{
+								Value:          "<h1>Test Template</h1>",
+								Representation: "storage",
+							},
+						},
+						Description: "This is a test template",
+						Space: &models.SpaceScheme{
+							Key: "TEST",
+						},
+					},
+				).
+					Return(&http.Request{}, nil)
+
+				client.On("Call", &http.Request{}, &models.ContentTemplateScheme{}).
+					Return(&models.ResponseScheme{Code: 200}, nil)
+
+				fields.c = client
+			},
+			wantErr: false,
+		},
+	}
+	for _, testCase := range tests {
+		t.Run(testCase.name, func(t *testing.T) {
+			if testCase.on != nil {
+				testCase.on(&testCase.fields)
+			}
+
+			newService := NewTemplateService(testCase.fields.c)
+
+			gotResult, gotResponse, err := newService.Create(testCase.args.ctx, testCase.args.payload)
+
+			if testCase.wantErr {
+				assert.EqualError(t, err, testCase.Err.Error())
+			} else {
+				assert.NoError(t, err)
+				assert.NotEqual(t, gotResponse, nil)
+				assert.NotEqual(t, gotResult, nil)
+			}
+		})
+	}
+}
+
+func Test_internalTemplateImpl_Update(t *testing.T) {
+	type fields struct {
+		c service.Connector
+	}
+	type args struct {
+		ctx     context.Context
+		payload *models.UpdateTemplateScheme
+	}
+	tests := []struct {
+		name    string
+		fields  fields
+		args    args
+		on      func(*fields)
+		wantErr bool
+		Err     error
+	}{
+		{
+			name: "CreateTemplateScheme.Create returned error",
+			args: args{
+				ctx: context.Background(),
+				payload: &models.UpdateTemplateScheme{
+					TemplateID:   "1234567",
+					Name:         "Test Template",
+					TemplateType: "page",
+					Body: &models.ContentTemplateBodyCreateScheme{
+						View: &models.ContentBodyCreateScheme{
+							Value:          "<h1>Test Template</h1>",
+							Representation: "storage",
+						},
+					},
+					Description: "This is a test template",
+					Space: &models.SpaceScheme{
+						Key: "TEST",
+					},
+				},
+			},
+			on: func(fields *fields) {
+				client := mocks.NewConnector(t)
+
+				client.On("NewRequest",
+					context.Background(),
+					http.MethodPut,
+					"/wiki/rest/api/template",
+					"",
+					&models.UpdateTemplateScheme{
+						TemplateID:   "1234567",
+						Name:         "Test Template",
+						TemplateType: "page",
+						Body: &models.ContentTemplateBodyCreateScheme{
+							View: &models.ContentBodyCreateScheme{
+								Value:          "<h1>Test Template</h1>",
+								Representation: "storage",
+							},
+						},
+						Description: "This is a test template",
+						Space: &models.SpaceScheme{
+							Key: "TEST",
+						},
+					},
+				).
+					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: "CreateTemplateScheme.Create success",
+			args: args{
+				ctx: context.Background(),
+				payload: &models.UpdateTemplateScheme{
+					TemplateID:   "123456789",
+					Name:         "Test Template",
+					TemplateType: "page",
+					Body: &models.ContentTemplateBodyCreateScheme{
+						View: &models.ContentBodyCreateScheme{
+							Value:          "<h1>Test Template</h1>",
+							Representation: "storage",
+						},
+					},
+					Description: "This is a test template",
+					Space: &models.SpaceScheme{
+						Key: "TEST",
+					},
+				},
+			},
+			on: func(fields *fields) {
+				client := mocks.NewConnector(t)
+
+				client.On("NewRequest",
+					context.Background(),
+					http.MethodPut,
+					"/wiki/rest/api/template",
+					"",
+					&models.UpdateTemplateScheme{
+						TemplateID:   "123456789",
+						Name:         "Test Template",
+						TemplateType: "page",
+						Body: &models.ContentTemplateBodyCreateScheme{
+							View: &models.ContentBodyCreateScheme{
+								Value:          "<h1>Test Template</h1>",
+								Representation: "storage",
+							},
+						},
+						Description: "This is a test template",
+						Space: &models.SpaceScheme{
+							Key: "TEST",
+						},
+					},
+				).
+					Return(&http.Request{}, nil)
+
+				client.On("Call", &http.Request{}, &models.ContentTemplateScheme{}).
+					Return(&models.ResponseScheme{Code: 200}, nil)
+
+				fields.c = client
+			},
+			wantErr: false,
+		},
+	}
+	for _, testCase := range tests {
+		t.Run(testCase.name, func(t *testing.T) {
+			if testCase.on != nil {
+				testCase.on(&testCase.fields)
+			}
+
+			newService := NewTemplateService(testCase.fields.c)
+
+			gotResult, gotResponse, err := newService.Update(testCase.args.ctx, testCase.args.payload)
+
+			if testCase.wantErr {
+				assert.EqualError(t, err, testCase.Err.Error())
+			} else {
+				assert.NoError(t, err)
+				assert.NotEqual(t, gotResponse, nil)
+				assert.NotEqual(t, gotResult, nil)
+			}
+		})
+	}
+}
+
+func Test_internalTemplateImpl_Get(t *testing.T) {
+	type fields struct {
+		c service.Connector
+	}
+	type args struct {
+		ctx        context.Context
+		templateID string
+	}
+	tests := []struct {
+		name    string
+		fields  fields
+		args    args
+		on      func(*fields)
+		wantErr bool
+		Err     error
+	}{
+		{
+			name: "CreateTemplateScheme.Create returned error",
+			args: args{
+				ctx:        context.Background(),
+				templateID: "123456",
+			},
+			on: func(fields *fields) {
+				client := mocks.NewConnector(t)
+
+				client.On("NewRequest",
+					context.Background(),
+					http.MethodGet,
+					"/wiki/rest/api/template/123456",
+					"",
+					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: "CreateTemplateScheme.Create success",
+			args: args{
+				ctx:        context.Background(),
+				templateID: "123456789",
+			},
+			on: func(fields *fields) {
+				client := mocks.NewConnector(t)
+
+				client.On("NewRequest",
+					context.Background(),
+					http.MethodGet,
+					"/wiki/rest/api/template/123456789",
+					"",
+					nil,
+				).
+					Return(&http.Request{}, nil)
+
+				client.On("Call", &http.Request{}, &models.ContentTemplateScheme{}).
+					Return(&models.ResponseScheme{Code: 200}, nil)
+
+				fields.c = client
+			},
+			wantErr: false,
+		},
+	}
+	for _, testCase := range tests {
+		t.Run(testCase.name, func(t *testing.T) {
+			if testCase.on != nil {
+				testCase.on(&testCase.fields)
+			}
+
+			newService := NewTemplateService(testCase.fields.c)
+
+			gotResult, gotResponse, err := newService.Get(testCase.args.ctx, testCase.args.templateID)
+
+			if testCase.wantErr {
+				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/v2/api_client_impl_test.go b/confluence/v2/api_client_impl_test.go
index d6a5001..f114f39 100644
--- a/confluence/v2/api_client_impl_test.go
+++ b/confluence/v2/api_client_impl_test.go
@@ -1,4 +1,4 @@
-package confluence
+package v2
 
 import (
 	"bytes"
diff --git a/jira/internal/issue_archive_impl_test.go b/jira/internal/issue_archive_impl_test.go
new file mode 100644
index 0000000..5be4eaf
--- /dev/null
+++ b/jira/internal/issue_archive_impl_test.go
@@ -0,0 +1,426 @@
+package internal
+
+import (
+	"context"
+	"errors"
+	model "github.com/ctreminiom/go-atlassian/v2/pkg/infra/models"
+	"github.com/ctreminiom/go-atlassian/v2/service"
+	"github.com/ctreminiom/go-atlassian/v2/service/mocks"
+	"github.com/stretchr/testify/assert"
+	"net/http"
+	"testing"
+)
+
+func Test_internalIssueArchivalImpl_Preserve(t *testing.T) {
+
+	type fields struct {
+		c       service.Connector
+		version string
+	}
+	type args struct {
+		ctx            context.Context
+		issueIdsOrKeys []string
+	}
+
+	tests := []struct {
+		name    string
+		fields  fields
+		args    args
+		on      func(*fields)
+		wantErr bool
+		Err     error
+	}{
+		{
+			name:   "happy path - when the issue list is archived successfully",
+			fields: fields{version: "2"},
+			args: args{
+				ctx:            context.Background(),
+				issueIdsOrKeys: []string{"KP-1"},
+			},
+			on: func(fields *fields) {
+
+				client := mocks.NewConnector(t)
+
+				client.On("NewRequest",
+					context.Background(),
+					http.MethodPut,
+					"rest/api/2/issue/archive", "", map[string]interface{}{"issueIdsOrKeys": []string{"KP-1"}}).
+					Return(&http.Request{}, nil)
+
+				client.On("Call",
+					&http.Request{},
+					&model.IssueArchivalSyncResponseScheme{}).
+					Return(&model.ResponseScheme{}, nil)
+
+				fields.c = client
+			},
+		},
+
+		{
+			name:   "fail path - when the issue list is not provided",
+			fields: fields{version: "2"},
+			args: args{
+				ctx:            context.Background(),
+				issueIdsOrKeys: nil,
+			},
+			wantErr: true,
+			Err:     model.ErrNoIssuesSlice,
+		},
+
+		{
+			name:   "fail path - when the http request cannot be created",
+			fields: fields{version: "2"},
+			args: args{
+				ctx:            context.Background(),
+				issueIdsOrKeys: []string{"KP-1"},
+			},
+			on: func(fields *fields) {
+
+				client := mocks.NewConnector(t)
+
+				client.On("NewRequest",
+					context.Background(),
+					http.MethodPut,
+					"rest/api/2/issue/archive", "", map[string]interface{}{"issueIdsOrKeys": []string{"KP-1"}}).
+					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 _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+
+			if tt.on != nil {
+				tt.on(&tt.fields)
+			}
+
+			archiveService := NewIssueArchivalService(tt.fields.c, tt.fields.version)
+
+			gotResult, gotResponse, err := archiveService.internalClient.Preserve(tt.args.ctx, tt.args.issueIdsOrKeys)
+
+			if tt.wantErr {
+				assert.Error(t, err)
+				assert.EqualError(t, err, tt.Err.Error())
+				return
+			}
+
+			assert.NoError(t, err)
+			assert.NotNil(t, gotResponse)
+			assert.NotNil(t, gotResult)
+		})
+	}
+}
+
+func Test_internalIssueArchivalImpl_PreserveByJQL(t *testing.T) {
+	type fields struct {
+		c       service.Connector
+		version string
+	}
+	type args struct {
+		ctx context.Context
+		jql string
+	}
+
+	tests := []struct {
+		name    string
+		fields  fields
+		args    args
+		on      func(*fields)
+		wantErr bool
+		Err     error
+	}{
+		{
+			name:   "happy path - when the issues are archived successfully",
+			fields: fields{version: "2"},
+			args: args{
+				ctx: context.Background(),
+				jql: "project = KP",
+			},
+			on: func(fields *fields) {
+
+				client := mocks.NewConnector(t)
+
+				client.On("NewRequest",
+					context.Background(),
+					http.MethodPost,
+					"rest/api/2/issue/archive", "", map[string]interface{}{"jql": "project = KP"}).
+					Return(&http.Request{}, nil)
+
+				client.On("Call",
+					&http.Request{},
+					nil).
+					Return(&model.ResponseScheme{}, nil)
+
+				fields.c = client
+			},
+		},
+		{
+			name:   "fail path - when the jql is not provided",
+			fields: fields{version: "2"},
+			args: args{
+				ctx: context.Background(),
+				jql: "",
+			},
+			wantErr: true,
+			Err:     model.ErrNoJQL,
+		},
+		{
+			name:   "fail path - when the http request cannot be created",
+			fields: fields{version: "2"},
+			args: args{
+				ctx: context.Background(),
+				jql: "project = KP",
+			},
+			on: func(fields *fields) {
+
+				client := mocks.NewConnector(t)
+
+				client.On("NewRequest",
+					context.Background(),
+					http.MethodPost,
+					"rest/api/2/issue/archive", "", map[string]interface{}{"jql": "project = KP"}).
+					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 _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+
+			if tt.on != nil {
+				tt.on(&tt.fields)
+			}
+
+			archiveService := NewIssueArchivalService(tt.fields.c, tt.fields.version)
+
+			gotResult, gotResponse, err := archiveService.internalClient.PreserveByJQL(tt.args.ctx, tt.args.jql)
+
+			if tt.wantErr {
+				assert.Error(t, err)
+				assert.EqualError(t, err, tt.Err.Error())
+				return
+			}
+
+			assert.NoError(t, err)
+			assert.NotNil(t, gotResponse)
+			assert.NotNil(t, gotResult)
+		})
+	}
+}
+
+func Test_internalIssueArchivalImpl_Restore(t *testing.T) {
+	type fields struct {
+		c       service.Connector
+		version string
+	}
+	type args struct {
+		ctx            context.Context
+		issueIDsOrKeys []string
+	}
+	tests := []struct {
+		name    string
+		fields  fields
+		args    args
+		on      func(*fields)
+		wantErr bool
+		Err     error
+	}{
+		{
+			name:   "happy path - when the issues are restored successfully",
+			fields: fields{version: "2"},
+			args: args{
+				ctx:            context.Background(),
+				issueIDsOrKeys: []string{"KP-1"},
+			},
+			on: func(fields *fields) {
+
+				client := mocks.NewConnector(t)
+
+				client.On("NewRequest",
+					context.Background(),
+					http.MethodPut,
+					"rest/api/2/issue/unarchive", "", map[string]interface{}{"issueIdsOrKeys": []string{"KP-1"}}).
+					Return(&http.Request{}, nil)
+
+				client.On("Call",
+					&http.Request{},
+					&model.IssueArchivalSyncResponseScheme{}).
+					Return(&model.ResponseScheme{}, nil)
+
+				fields.c = client
+			},
+		},
+		{
+			name:   "fail path - when the issue list is not provided",
+			fields: fields{version: "2"},
+			args: args{
+				ctx:            context.Background(),
+				issueIDsOrKeys: nil,
+			},
+			wantErr: true,
+			Err:     model.ErrNoIssuesSlice,
+		},
+		{
+			name:   "fail path - when the http request cannot be created",
+			fields: fields{version: "2"},
+			args: args{
+				ctx:            context.Background(),
+				issueIDsOrKeys: []string{"KP-1"},
+			},
+			on: func(fields *fields) {
+
+				client := mocks.NewConnector(t)
+
+				client.On("NewRequest",
+					context.Background(),
+					http.MethodPut,
+					"rest/api/2/issue/unarchive", "", map[string]interface{}{"issueIdsOrKeys": []string{"KP-1"}}).
+					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 _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+
+			if tt.on != nil {
+				tt.on(&tt.fields)
+			}
+
+			archiveService := NewIssueArchivalService(tt.fields.c, tt.fields.version)
+
+			gotResult, gotResponse, err := archiveService.internalClient.Restore(tt.args.ctx, tt.args.issueIDsOrKeys)
+
+			if tt.wantErr {
+				assert.Error(t, err)
+				assert.EqualError(t, err, tt.Err.Error())
+				return
+			}
+
+			assert.NoError(t, err)
+			assert.NotNil(t, gotResponse)
+			assert.NotNil(t, gotResult)
+
+		})
+	}
+}
+
+func Test_internalIssueArchivalImpl_Export(t *testing.T) {
+	type fields struct {
+		c       service.Connector
+		version string
+	}
+	type args struct {
+		ctx     context.Context
+		payload *model.IssueArchivalExportPayloadScheme
+	}
+	tests := []struct {
+		name    string
+		fields  fields
+		args    args
+		on      func(*fields)
+		wantErr bool
+		Err     error
+	}{
+		{
+			name:   "happy path - when the issues are exported successfully",
+			fields: fields{version: "2"},
+			args: args{
+				ctx: context.Background(),
+				payload: &model.IssueArchivalExportPayloadScheme{
+
+					ArchivedBy: []string{
+						"uuid-sample",
+						"uuid-sample",
+					},
+					ArchivedDateRange: &model.DateRangeFilterRequestScheme{
+						DateAfter:  "2023-01-01",
+						DateBefore: "2023-01-12",
+					},
+					IssueTypes: []string{"Bug", "Story"},
+					Projects:   []string{"WORK"},
+					Reporters:  []string{"uuid-sample"},
+				},
+			},
+			on: func(fields *fields) {
+
+				client := mocks.NewConnector(t)
+
+				client.On("NewRequest",
+					context.Background(),
+					http.MethodPut,
+					"rest/api/2/issues/archive/export", "", &model.IssueArchivalExportPayloadScheme{
+
+						ArchivedBy: []string{
+							"uuid-sample",
+							"uuid-sample",
+						},
+						ArchivedDateRange: &model.DateRangeFilterRequestScheme{
+							DateAfter:  "2023-01-01",
+							DateBefore: "2023-01-12",
+						},
+						IssueTypes: []string{"Bug", "Story"},
+						Projects:   []string{"WORK"},
+						Reporters:  []string{"uuid-sample"},
+					}).
+					Return(&http.Request{}, nil)
+
+				client.On("Call",
+					&http.Request{},
+					&model.IssueArchiveExportResultScheme{}).
+					Return(&model.ResponseScheme{}, nil)
+
+				fields.c = client
+			},
+		},
+		{
+			name:   "fail path - when the http request cannot be created",
+			fields: fields{version: "2"},
+			args: args{
+				ctx:     context.Background(),
+				payload: &model.IssueArchivalExportPayloadScheme{},
+			},
+			on: func(fields *fields) {
+
+				client := mocks.NewConnector(t)
+
+				client.On("NewRequest",
+					context.Background(),
+					http.MethodPut,
+					"rest/api/2/issues/archive/export", "", &model.IssueArchivalExportPayloadScheme{}).
+					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 _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+
+			if tt.on != nil {
+				tt.on(&tt.fields)
+			}
+
+			archiveService := NewIssueArchivalService(tt.fields.c, tt.fields.version)
+
+			gotResult, gotResponse, err := archiveService.internalClient.Export(tt.args.ctx, tt.args.payload)
+
+			if tt.wantErr {
+				assert.Error(t, err)
+				assert.EqualError(t, err, tt.Err.Error())
+				return
+			}
+
+			assert.NoError(t, err)
+			assert.NotNil(t, gotResponse)
+			assert.NotNil(t, gotResult)
+		})
+	}
+}
diff --git a/jira/internal/watcher_impl_test.go b/jira/internal/watcher_impl_test.go
index f55c136..0b3f4c2 100644
--- a/jira/internal/watcher_impl_test.go
+++ b/jira/internal/watcher_impl_test.go
@@ -199,7 +199,7 @@ func Test_internalWatcherImpl_Add(t *testing.T) {
 					http.MethodPost,
 					"rest/api/3/issue/DUMMY-5/watchers",
 					"",
-					[]byte(nil)).
+					"").
 					Return(&http.Request{}, nil)
 
 				client.On("Call",
@@ -230,7 +230,7 @@ func Test_internalWatcherImpl_Add(t *testing.T) {
 					http.MethodPost,
 					"rest/api/3/issue/DUMMY-5/watchers",
 					"",
-					[]byte("someAccountID")).
+					"someAccountID").
 					Return(&http.Request{}, nil)
 
 				client.On("Call",
@@ -261,7 +261,7 @@ func Test_internalWatcherImpl_Add(t *testing.T) {
 					http.MethodPost,
 					"rest/api/2/issue/DUMMY-5/watchers",
 					"",
-					[]byte(nil),
+					"",
 				).
 					Return(&http.Request{}, nil)
 
@@ -293,7 +293,7 @@ func Test_internalWatcherImpl_Add(t *testing.T) {
 					http.MethodPost,
 					"rest/api/2/issue/DUMMY-5/watchers",
 					"",
-					[]byte("someAccountID")).
+					"someAccountID").
 					Return(&http.Request{}, nil)
 
 				client.On("Call",
@@ -335,7 +335,7 @@ func Test_internalWatcherImpl_Add(t *testing.T) {
 					http.MethodPost,
 					"rest/api/3/issue/DUMMY-5/watchers",
 					"",
-					[]byte(nil)).
+					"").
 					Return(&http.Request{}, errors.New("error, unable to create the http request"))
 
 				fields.c = client