@agility/management-sdk 0.1.22 → 0.1.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursor/manifest.md +65 -0
- package/.cursor/rules/agility_cms_sdk_rules.mdc +155 -0
- package/.cursor/rules/asset_management.mdc +97 -0
- package/.cursor/rules/common_patterns.mdc +5 -0
- package/.cursor/rules/configuration.mdc +5 -0
- package/.cursor/rules/content_management.mdc +5 -0
- package/.cursor/rules/page_management.mdc +5 -0
- package/.cursor/rules/security.mdc +5 -0
- package/.cursor/rules/setup_and_auth.mdc +5 -0
- package/.cursor/rules/testing_and_debugging.mdc +5 -0
- package/.cursor/rules/webhook_management.mdc +61 -0
- package/.env +11 -0
- package/README.md +84 -2
- package/dist/apiMethods/containerMethods.d.ts +1 -1
- package/dist/apiMethods/containerMethods.js +4 -3
- package/dist/apiMethods/contentMethods.d.ts +2 -2
- package/dist/apiMethods/contentMethods.js +8 -0
- package/dist/apiMethods/pageMethods.d.ts +1 -1
- package/dist/apiMethods/pageMethods.js +4 -0
- package/dist/models/tokenResponseData.d.ts +11 -0
- package/dist/models/tokenResponseData.js +13 -0
- package/dist/tests/instance.test.d.ts +1 -0
- package/dist/tests/instance.test.js +99 -0
- package/dist/tests/sdk.test.d.ts +1 -0
- package/dist/tests/sdk.test.js +78 -0
- package/environment.example +12 -0
- package/package.json +1 -1
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Agility CMS Management SDK Test Suite Plan
|
|
2
|
+
|
|
3
|
+
## Setup
|
|
4
|
+
|
|
5
|
+
* [x] Choose and install a test runner (Suggesting Jest).
|
|
6
|
+
* [x] Configure test scripts in `package.json`.
|
|
7
|
+
* [x] Set up test environment configuration (handling API keys, GUIDs etc., likely via environment variables).
|
|
8
|
+
* Using `.env` file for configuration.
|
|
9
|
+
* Implemented Azure Table Storage refresh token flow in `src/tests/authHelper.ts`.
|
|
10
|
+
|
|
11
|
+
## Core Functionality Tests
|
|
12
|
+
|
|
13
|
+
* [x] Test SDK client initialization (`ApiClient` class).
|
|
14
|
+
* Tested via `src/tests/authHelper.ts` integration in `src/tests/sdk.test.ts`.
|
|
15
|
+
* [ ] Test basic read operations (e.g., getting models, locales).
|
|
16
|
+
|
|
17
|
+
## CRUD Operation Tests (Integration Tests)
|
|
18
|
+
|
|
19
|
+
* **Content Items**
|
|
20
|
+
* [ ] Get Content List
|
|
21
|
+
* [ ] Get Content Item by ID
|
|
22
|
+
* [ ] Create Content Item
|
|
23
|
+
* [ ] Update Content Item
|
|
24
|
+
* [ ] Delete Content Item
|
|
25
|
+
* [ ] Publish Content Item
|
|
26
|
+
* [ ] Unpublish Content Item
|
|
27
|
+
* [ ] Request Content Approval
|
|
28
|
+
* **Content Models**
|
|
29
|
+
* [ ] Get Models List
|
|
30
|
+
* [ ] Get Model by ID
|
|
31
|
+
* [ ] Save Model (Create/Update)
|
|
32
|
+
* [ ] Delete Model
|
|
33
|
+
* **Assets**
|
|
34
|
+
* [ ] Get Assets List
|
|
35
|
+
* [ ] Upload Asset
|
|
36
|
+
* [ ] Get Asset by ID
|
|
37
|
+
* [ ] Delete Asset
|
|
38
|
+
* **Containers**
|
|
39
|
+
* [ ] Get Containers List
|
|
40
|
+
* [ ] Get Container by ID
|
|
41
|
+
* [ ] Save Container (Create/Update)
|
|
42
|
+
* [ ] Delete Container
|
|
43
|
+
* **Pages**
|
|
44
|
+
* [ ] Get Pages List
|
|
45
|
+
* [ ] Get Page by ID
|
|
46
|
+
* [ ] Create Page
|
|
47
|
+
* [ ] Update Page
|
|
48
|
+
* [ ] Delete Page
|
|
49
|
+
* [ ] Publish Page
|
|
50
|
+
* [ ] Unpublish Page
|
|
51
|
+
* [ ] Request Page Approval
|
|
52
|
+
* **Sync/Batch Operations**
|
|
53
|
+
* [ ] Test Sync Endpoints
|
|
54
|
+
* [ ] Test Batch Endpoints
|
|
55
|
+
* **Other Endpoints**
|
|
56
|
+
* [ ] Test URL Redirection endpoints
|
|
57
|
+
* [ ] Test Site Map endpoints
|
|
58
|
+
* [ ] Test Webhook endpoints
|
|
59
|
+
|
|
60
|
+
## Refinement
|
|
61
|
+
|
|
62
|
+
* [ ] Organize tests logically (e.g., by SDK module/feature).
|
|
63
|
+
* [ ] Ensure tests clean up created resources (delete items, models, etc.).
|
|
64
|
+
* [ ] Add tests for error handling scenarios.
|
|
65
|
+
* [ ] Review structure for Turborepo compatibility (e.g., tests in a `src/__tests__` or root `tests` folder).
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
---
|
|
2
|
+
description:
|
|
3
|
+
globs:
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
# Agility CMS Management SDK Rules Overview
|
|
7
|
+
|
|
8
|
+
## Introduction
|
|
9
|
+
This document serves as the central reference for all Agility CMS Management SDK rules. These rules are designed to help developers implement the SDK effectively, securely, and efficiently.
|
|
10
|
+
|
|
11
|
+
## Rule Categories
|
|
12
|
+
|
|
13
|
+
### 1. Core Functionality
|
|
14
|
+
- [Setup and Authentication](mdc:setup_and_auth.mdc)
|
|
15
|
+
- Authentication flow
|
|
16
|
+
- Token management
|
|
17
|
+
- SDK initialization
|
|
18
|
+
- Best practices
|
|
19
|
+
|
|
20
|
+
- [Content Management](mdc:content_management.mdc)
|
|
21
|
+
- Content operations
|
|
22
|
+
- Publishing workflow
|
|
23
|
+
- Content retrieval
|
|
24
|
+
- Batch operations
|
|
25
|
+
|
|
26
|
+
- [Page Management](mdc:page_management.mdc)
|
|
27
|
+
- Page operations
|
|
28
|
+
- Template management
|
|
29
|
+
- Sitemap handling
|
|
30
|
+
- Publishing workflow
|
|
31
|
+
|
|
32
|
+
- [Asset Management](mdc:asset_management.mdc)
|
|
33
|
+
- File uploads
|
|
34
|
+
- Folder management
|
|
35
|
+
- Asset retrieval
|
|
36
|
+
- Gallery operations
|
|
37
|
+
|
|
38
|
+
- [Webhook Management](mdc:webhook_management.mdc)
|
|
39
|
+
- Webhook setup
|
|
40
|
+
- Event handling
|
|
41
|
+
- Security considerations
|
|
42
|
+
- Best practices
|
|
43
|
+
|
|
44
|
+
### 2. Project Structure
|
|
45
|
+
- [Configuration](mdc:configuration.mdc)
|
|
46
|
+
- Environment setup
|
|
47
|
+
- SDK initialization
|
|
48
|
+
- Error handling
|
|
49
|
+
- Logging
|
|
50
|
+
|
|
51
|
+
- [Common Patterns](mdc:common_patterns.mdc)
|
|
52
|
+
- Content caching
|
|
53
|
+
- Batch operations
|
|
54
|
+
- Page hierarchy
|
|
55
|
+
- Asset optimization
|
|
56
|
+
- Webhook patterns
|
|
57
|
+
|
|
58
|
+
- [Testing and Debugging](mdc:testing_and_debugging.mdc)
|
|
59
|
+
- Unit testing
|
|
60
|
+
- Integration testing
|
|
61
|
+
- Debugging tools
|
|
62
|
+
- Performance testing
|
|
63
|
+
|
|
64
|
+
- [Security](mdc:security.mdc)
|
|
65
|
+
- Authentication security
|
|
66
|
+
- API security
|
|
67
|
+
- Data security
|
|
68
|
+
- Webhook security
|
|
69
|
+
|
|
70
|
+
## Getting Started
|
|
71
|
+
|
|
72
|
+
### Quick Start
|
|
73
|
+
1. Set up your environment variables (see [Configuration](mdc:configuration.mdc))
|
|
74
|
+
2. Initialize the SDK (see [Setup and Authentication](mdc:setup_and_auth.mdc))
|
|
75
|
+
3. Start implementing your features using the appropriate rule files
|
|
76
|
+
|
|
77
|
+
### Best Practices
|
|
78
|
+
1. Always follow security guidelines (see [Security](mdc:security.mdc))
|
|
79
|
+
2. Implement proper error handling (see [Configuration](mdc:configuration.mdc))
|
|
80
|
+
3. Use common patterns for efficiency (see [Common Patterns](mdc:common_patterns.mdc))
|
|
81
|
+
4. Write comprehensive tests (see [Testing and Debugging](mdc:testing_and_debugging.mdc))
|
|
82
|
+
|
|
83
|
+
## Implementation Guide
|
|
84
|
+
|
|
85
|
+
### Content Management
|
|
86
|
+
```typescript
|
|
87
|
+
// See content_management.mdc for details
|
|
88
|
+
const content = await apiClient.contentMethods.getContentItem(contentID, guid, locale);
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Page Management
|
|
92
|
+
```typescript
|
|
93
|
+
// See page_management.mdc for details
|
|
94
|
+
const page = await apiClient.pageMethods.getPage(pageID, guid, locale);
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Asset Management
|
|
98
|
+
```typescript
|
|
99
|
+
// See asset_management.mdc for details
|
|
100
|
+
await apiClient.assetMethods.upload(formData, path, guid, groupingID);
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Webhook Setup
|
|
104
|
+
```typescript
|
|
105
|
+
// See webhook_management.mdc for details
|
|
106
|
+
const webhook = await apiClient.webhookMethods.saveWebhook(guid, webhookConfig);
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Common Scenarios
|
|
110
|
+
|
|
111
|
+
### 1. Content Publishing Workflow
|
|
112
|
+
1. Create content (see [Content Management](mdc:content_management.mdc))
|
|
113
|
+
2. Request approval
|
|
114
|
+
3. Publish content
|
|
115
|
+
4. Monitor status
|
|
116
|
+
|
|
117
|
+
### 2. Page Management
|
|
118
|
+
1. Create page template (see [Page Management](mdc:page_management.mdc))
|
|
119
|
+
2. Create page
|
|
120
|
+
3. Add content
|
|
121
|
+
4. Publish page
|
|
122
|
+
|
|
123
|
+
### 3. Asset Management
|
|
124
|
+
1. Create folder structure (see [Asset Management](mdc:asset_management.mdc))
|
|
125
|
+
2. Upload assets
|
|
126
|
+
3. Organize in galleries
|
|
127
|
+
4. Link to content
|
|
128
|
+
|
|
129
|
+
### 4. Webhook Implementation
|
|
130
|
+
1. Set up webhook (see [Webhook Management](mdc:webhook_management.mdc))
|
|
131
|
+
2. Implement handlers
|
|
132
|
+
3. Test integration
|
|
133
|
+
4. Monitor events
|
|
134
|
+
|
|
135
|
+
## Troubleshooting
|
|
136
|
+
- Check [Testing and Debugging](mdc:testing_and_debugging.mdc) for debugging tools
|
|
137
|
+
- Review [Common Patterns](mdc:common_patterns.mdc) for known issues
|
|
138
|
+
- Consult [Security](mdc:security.mdc) for security-related issues
|
|
139
|
+
|
|
140
|
+
## Additional Resources
|
|
141
|
+
- [Agility CMS Documentation](mdc:https:/help.agilitycms.com/hc/en-us)
|
|
142
|
+
- [TypeScript Documentation](mdc:https:/www.typescriptlang.org/docs)
|
|
143
|
+
- [Community Forums](mdc:https:/help.agilitycms.com/hc/en-us/community/topics)
|
|
144
|
+
|
|
145
|
+
## Contributing
|
|
146
|
+
If you have additional rules or improvements to suggest:
|
|
147
|
+
1. Create a new rule file
|
|
148
|
+
2. Update this central reference
|
|
149
|
+
3. Submit a pull request
|
|
150
|
+
|
|
151
|
+
## Version History
|
|
152
|
+
- v1.0.0: Initial release
|
|
153
|
+
- v1.1.0: Added security rules
|
|
154
|
+
- v1.2.0: Added common patterns
|
|
155
|
+
- v1.3.0: Added testing and debugging rules
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
---
|
|
2
|
+
description:
|
|
3
|
+
globs:
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
# Agility CMS Asset Management Rules
|
|
7
|
+
|
|
8
|
+
## Asset Operations
|
|
9
|
+
- Always provide required parameters: guid, mediaID
|
|
10
|
+
- Use appropriate methods for different asset operations
|
|
11
|
+
- Handle file uploads and management properly
|
|
12
|
+
|
|
13
|
+
## File Upload
|
|
14
|
+
1. Upload single file:
|
|
15
|
+
```typescript
|
|
16
|
+
const form = new FormData();
|
|
17
|
+
form.append('files', file, fileName);
|
|
18
|
+
await apiClient.assetMethods.upload(form, agilityFolderPath, guid, groupingID);
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
2. Upload multiple files:
|
|
22
|
+
```typescript
|
|
23
|
+
const form = new FormData();
|
|
24
|
+
files.forEach(file => {
|
|
25
|
+
form.append('files', file, file.name);
|
|
26
|
+
});
|
|
27
|
+
await apiClient.assetMethods.upload(form, agilityFolderPath, guid, groupingID);
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Folder Management
|
|
31
|
+
1. Create folder:
|
|
32
|
+
```typescript
|
|
33
|
+
await apiClient.assetMethods.createFolder(originKey, guid);
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
2. Delete folder:
|
|
37
|
+
```typescript
|
|
38
|
+
await apiClient.assetMethods.deleteFolder(originKey, guid, mediaID);
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
3. Rename folder:
|
|
42
|
+
```typescript
|
|
43
|
+
await apiClient.assetMethods.renameFolder(folderName, newFolderName, guid, mediaID);
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## File Operations
|
|
47
|
+
1. Delete file:
|
|
48
|
+
```typescript
|
|
49
|
+
await apiClient.assetMethods.deleteFile(mediaID, guid);
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
2. Move file:
|
|
53
|
+
```typescript
|
|
54
|
+
await apiClient.assetMethods.moveFile(mediaID, newFolder, guid);
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Asset Retrieval
|
|
58
|
+
1. Get media list:
|
|
59
|
+
```typescript
|
|
60
|
+
const mediaList = await apiClient.assetMethods.getMediaList(pageSize, recordOffset, guid);
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
2. Get asset by ID:
|
|
64
|
+
```typescript
|
|
65
|
+
const asset = await apiClient.assetMethods.getAssetByID(mediaID, guid);
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
3. Get asset by URL:
|
|
69
|
+
```typescript
|
|
70
|
+
const asset = await apiClient.assetMethods.getAssetByUrl(url, guid);
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Gallery Management
|
|
74
|
+
1. Get gallery by ID:
|
|
75
|
+
```typescript
|
|
76
|
+
const gallery = await apiClient.assetMethods.getGalleryById(guid, id);
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
2. Get gallery by name:
|
|
80
|
+
```typescript
|
|
81
|
+
const gallery = await apiClient.assetMethods.getGalleryByName(guid, galleryName);
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
3. Save gallery:
|
|
85
|
+
```typescript
|
|
86
|
+
await apiClient.assetMethods.saveGallery(guid, gallery);
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Best Practices
|
|
90
|
+
- Validate file types before upload
|
|
91
|
+
- Implement proper error handling
|
|
92
|
+
- Use appropriate folder structure
|
|
93
|
+
- Optimize file sizes before upload
|
|
94
|
+
- Use descriptive file names
|
|
95
|
+
- Implement proper access control
|
|
96
|
+
- Monitor storage usage
|
|
97
|
+
- Use appropriate file formats
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
description:
|
|
3
|
+
globs:
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
# Agility CMS Webhook Management Rules
|
|
7
|
+
|
|
8
|
+
## Webhook Operations
|
|
9
|
+
- Always provide required parameters: guid, webhookID
|
|
10
|
+
- Use appropriate methods for different webhook operations
|
|
11
|
+
- Handle webhook events properly
|
|
12
|
+
|
|
13
|
+
## Webhook Management
|
|
14
|
+
1. List webhooks:
|
|
15
|
+
```typescript
|
|
16
|
+
const webhooks = await apiClient.webhookMethods.webhookList(guid, take, skip);
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
2. Create webhook:
|
|
20
|
+
```typescript
|
|
21
|
+
const webhook = new Webhook();
|
|
22
|
+
// Set required properties
|
|
23
|
+
await apiClient.webhookMethods.saveWebhook(guid, webhook);
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
3. Get webhook:
|
|
27
|
+
```typescript
|
|
28
|
+
const webhook = await apiClient.webhookMethods.getWebhook(guid, webhookID);
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
4. Delete webhook:
|
|
32
|
+
```typescript
|
|
33
|
+
await apiClient.webhookMethods.deleteWebhook(guid, webhookID);
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Webhook Configuration
|
|
37
|
+
1. Required properties:
|
|
38
|
+
- URL endpoint
|
|
39
|
+
- Event types to listen for
|
|
40
|
+
- Authentication if required
|
|
41
|
+
- Retry policy
|
|
42
|
+
- Payload format
|
|
43
|
+
|
|
44
|
+
2. Event types:
|
|
45
|
+
- Content changes
|
|
46
|
+
- Page changes
|
|
47
|
+
- Asset changes
|
|
48
|
+
- User changes
|
|
49
|
+
- System events
|
|
50
|
+
|
|
51
|
+
## Best Practices
|
|
52
|
+
- Implement proper error handling
|
|
53
|
+
- Use HTTPS endpoints
|
|
54
|
+
- Validate webhook payloads
|
|
55
|
+
- Implement retry logic
|
|
56
|
+
- Monitor webhook delivery
|
|
57
|
+
- Secure webhook endpoints
|
|
58
|
+
- Log webhook events
|
|
59
|
+
- Handle rate limiting
|
|
60
|
+
- Use appropriate authentication
|
|
61
|
+
- Test webhook endpoints
|
package/.env
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
AGILITY_GUID=f3866b86-u
|
|
2
|
+
AGILITY_API_KEY=defaultpreview.2672a9e1f5f8fade023029763b540c64eabfab48f92e693fa01081d845783dca
|
|
3
|
+
AGILITY_LOCALES=en-us
|
|
4
|
+
AGILITY_SITEMAP=website
|
|
5
|
+
AGILITY_API_ISPREVIEW=true
|
|
6
|
+
|
|
7
|
+
AGILITY_MANAGEMENT_API_BASE_URL=https://mgmt.aglty.io
|
|
8
|
+
TEST_ENV=dev
|
|
9
|
+
STORAGE_ACCOUNT=agilitydevstorage
|
|
10
|
+
STORAGE_ACCOUNT_KEY=0gMvadhiKgr04J0u0dlui8gaH6rY8kRo46vNvG/JmGAmzhMqsQ7V+bonmZOVgqMOo9STt0xyEToHBpJFjVeR/w==
|
|
11
|
+
STORAGE_ACCOUNT_TABLE=TokenInfo
|
package/README.md
CHANGED
|
@@ -12,8 +12,82 @@
|
|
|
12
12
|
### Prerequisites
|
|
13
13
|
1. Clone the repository agility-cms-management-sdk-typescript.
|
|
14
14
|
2. Import the index file to make use of the Options class.
|
|
15
|
-
3.
|
|
16
|
-
|
|
15
|
+
3. You will need valid Agility CMS credentials to authenticate and obtain an access token.
|
|
16
|
+
|
|
17
|
+
### Authentication
|
|
18
|
+
Before using the SDK, you must authenticate against the Agility Management API to obtain a valid access token. This token is required for all subsequent API requests.
|
|
19
|
+
|
|
20
|
+
The authentication process uses OAuth 2.0 and requires multiple steps:
|
|
21
|
+
|
|
22
|
+
1. First, initiate the authorization flow by making a GET request to the authorization endpoint:
|
|
23
|
+
```javascript
|
|
24
|
+
const authUrl = 'https://mgmt.aglty.io/oauth/authorize';
|
|
25
|
+
const params = new URLSearchParams({
|
|
26
|
+
response_type: 'code',
|
|
27
|
+
redirect_uri: 'YOUR_REDIRECT_URI',
|
|
28
|
+
state: 'YOUR_STATE',
|
|
29
|
+
scope: 'openid profile email offline_access'
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Redirect the user to the authorization URL
|
|
33
|
+
window.location.href = `${authUrl}?${params.toString()}`;
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
2. After successful authentication, you'll receive an authorization code at your redirect URI. Use this code to obtain an access token:
|
|
37
|
+
```javascript
|
|
38
|
+
const response = await fetch('https://mgmt.aglty.io/oauth/token', {
|
|
39
|
+
method: 'POST',
|
|
40
|
+
headers: {
|
|
41
|
+
'Content-Type': 'application/x-www-form-urlencoded'
|
|
42
|
+
},
|
|
43
|
+
body: new URLSearchParams({
|
|
44
|
+
code: 'YOUR_AUTHORIZATION_CODE'
|
|
45
|
+
})
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const { access_token, refresh_token, expires_in } = await response.json();
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
3. Use the obtained token to initialize the SDK:
|
|
52
|
+
```javascript
|
|
53
|
+
import * as mgmtApi from "./index";
|
|
54
|
+
|
|
55
|
+
// Initialize the Options Class with your authentication token
|
|
56
|
+
let options = new mgmtApi.Options();
|
|
57
|
+
options.token = access_token; // Use the token obtained from authentication
|
|
58
|
+
|
|
59
|
+
// Initialize the APIClient Class
|
|
60
|
+
let apiClient = new mgmtApi.ApiClient(options);
|
|
61
|
+
|
|
62
|
+
let guid = "<<Provide the Guid of the Website>>";
|
|
63
|
+
let locale = "<<Provide the locale of the Website>>"; // Example: en-us
|
|
64
|
+
|
|
65
|
+
// Now you can make authenticated requests
|
|
66
|
+
var contentItem = await apiClient.contentMethods.getContentItem(22, guid, locale);
|
|
67
|
+
console.log(JSON.stringify(contentItem));
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
4. When the access token expires, use the refresh token to obtain a new access token:
|
|
71
|
+
```javascript
|
|
72
|
+
const response = await fetch('https://mgmt.aglty.io/oauth/refresh', {
|
|
73
|
+
method: 'POST',
|
|
74
|
+
headers: {
|
|
75
|
+
'Content-Type': 'application/json'
|
|
76
|
+
},
|
|
77
|
+
body: JSON.stringify({
|
|
78
|
+
refresh_token: 'YOUR_REFRESH_TOKEN'
|
|
79
|
+
})
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const { access_token, refresh_token, expires_in } = await response.json();
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Note:
|
|
86
|
+
- The access token has a limited lifetime (typically 1 hour)
|
|
87
|
+
- The refresh token can be used to obtain new access tokens
|
|
88
|
+
- Store refresh tokens securely and never expose them in client-side code
|
|
89
|
+
- Implement proper error handling for authentication failures
|
|
90
|
+
|
|
17
91
|
4. Create an object of Method class(es), which can be used to create and perform operations. Following is the description of Classes and their respective methods -
|
|
18
92
|
|
|
19
93
|
### Making a Request
|
|
@@ -662,6 +736,14 @@ Returns: An object with the requested Webhook.
|
|
|
662
736
|
| `guid` | `string` | Current website guid.|
|
|
663
737
|
| `webhookID` | `string` | The webhookID of the requested webhook.|
|
|
664
738
|
|
|
739
|
+
## Class Instance
|
|
740
|
+
|
|
741
|
+
### getLocales
|
|
742
|
+
| Parameter | Type | Description |
|
|
743
|
+
| :-------- | :------- | :------------------------- |
|
|
744
|
+
| `guid` | `string` | Current website guid.|
|
|
745
|
+
|
|
746
|
+
Returns: An array of locales. ex. ['en-us','fr-ca']
|
|
665
747
|
|
|
666
748
|
## Running the SDK Locally
|
|
667
749
|
|
|
@@ -12,6 +12,6 @@ export declare class ContainerMethods {
|
|
|
12
12
|
getContainerSecurity(id: number, guid: string): Promise<Container>;
|
|
13
13
|
getContainerList(guid: string): Promise<Container[]>;
|
|
14
14
|
getNotificationList(id: number, guid: string): Promise<Notification[]>;
|
|
15
|
-
saveContainer(container: Container, guid: string): Promise<Container>;
|
|
15
|
+
saveContainer(container: Container, guid: string, forceReferenceName?: boolean): Promise<Container>;
|
|
16
16
|
deleteContainer(id: number, guid: string): Promise<string>;
|
|
17
17
|
}
|
|
@@ -164,14 +164,15 @@ var ContainerMethods = /** @class */ (function () {
|
|
|
164
164
|
});
|
|
165
165
|
});
|
|
166
166
|
};
|
|
167
|
-
ContainerMethods.prototype.saveContainer = function (
|
|
168
|
-
return __awaiter(this,
|
|
167
|
+
ContainerMethods.prototype.saveContainer = function (container_1, guid_1) {
|
|
168
|
+
return __awaiter(this, arguments, void 0, function (container, guid, forceReferenceName) {
|
|
169
169
|
var apiPath, resp, err_7;
|
|
170
|
+
if (forceReferenceName === void 0) { forceReferenceName = false; }
|
|
170
171
|
return __generator(this, function (_a) {
|
|
171
172
|
switch (_a.label) {
|
|
172
173
|
case 0:
|
|
173
174
|
_a.trys.push([0, 2, , 3]);
|
|
174
|
-
apiPath = "container";
|
|
175
|
+
apiPath = "container".concat(forceReferenceName ? '?forceReferenceName=true' : '');
|
|
175
176
|
return [4 /*yield*/, this._clientInstance.executePost(apiPath, guid, this._options.token, container)];
|
|
176
177
|
case 1:
|
|
177
178
|
resp = _a.sent();
|
|
@@ -18,8 +18,8 @@ export declare class ContentMethods {
|
|
|
18
18
|
approveContent(contentID: number, guid: string, locale: string, comments?: string): Promise<number[]>;
|
|
19
19
|
declineContent(contentID: number, guid: string, locale: string, comments?: string): Promise<number[]>;
|
|
20
20
|
deleteContent(contentID: number, guid: string, locale: string, comments?: string): Promise<number[]>;
|
|
21
|
-
saveContentItem(contentItem: ContentItem, guid: string, locale: string): Promise<number[]>;
|
|
22
|
-
saveContentItems(contentItems: ContentItem[], guid: string, locale: string): Promise<number[]>;
|
|
21
|
+
saveContentItem(contentItem: ContentItem, guid: string, locale: string): Promise<import("..").Batch | number[]>;
|
|
22
|
+
saveContentItems(contentItems: ContentItem[], guid: string, locale: string): Promise<import("..").Batch | number[]>;
|
|
23
23
|
getContentItems(referenceName: string, guid: string, locale: string, listParams: ListParams): Promise<ContentList>;
|
|
24
24
|
getContentList(referenceName: string, guid: string, locale: string, listParams: ListParams, filterObject?: ContentListFilterModel): Promise<ContentList>;
|
|
25
25
|
getContentHistory(locale: string, guid: string, contentID: number, take?: number, skip?: number): Promise<ContentItemHistory>;
|
|
@@ -285,6 +285,10 @@ var ContentMethods = /** @class */ (function () {
|
|
|
285
285
|
}); }); })];
|
|
286
286
|
case 2:
|
|
287
287
|
batch = _a.sent();
|
|
288
|
+
// Aaaron May 1 2025 - if the batch has error data, return the batch not just the -1 contentID
|
|
289
|
+
if (batch.errorData) {
|
|
290
|
+
return [2 /*return*/, batch];
|
|
291
|
+
}
|
|
288
292
|
contentIDs_7 = [];
|
|
289
293
|
batch.items.forEach(function (element) { return contentIDs_7.push(element.itemID); });
|
|
290
294
|
return [2 /*return*/, contentIDs_7];
|
|
@@ -317,6 +321,10 @@ var ContentMethods = /** @class */ (function () {
|
|
|
317
321
|
}); }); })];
|
|
318
322
|
case 2:
|
|
319
323
|
batch = _a.sent();
|
|
324
|
+
// Aaaron May 1 2025 - if the batch has error data, return the batch not just the -1 contentID
|
|
325
|
+
if (batch.errorData) {
|
|
326
|
+
return [2 /*return*/, batch];
|
|
327
|
+
}
|
|
320
328
|
contentIDs_8 = [];
|
|
321
329
|
batch.items.forEach(function (element) { return contentIDs_8.push(element.itemID); });
|
|
322
330
|
return [2 /*return*/, contentIDs_8];
|
|
@@ -26,7 +26,7 @@ export declare class PageMethods {
|
|
|
26
26
|
approvePage(pageID: number, guid: string, locale: string, comments?: string): Promise<number[]>;
|
|
27
27
|
declinePage(pageID: number, guid: string, locale: string, comments?: string): Promise<number[]>;
|
|
28
28
|
deletePage(pageID: number, guid: string, locale: string, comments?: string): Promise<number[]>;
|
|
29
|
-
savePage(pageItem: PageItem, guid: string, locale: string, parentPageID?: number, placeBeforePageItemID?: number): Promise<number[]>;
|
|
29
|
+
savePage(pageItem: PageItem, guid: string, locale: string, parentPageID?: number, placeBeforePageItemID?: number): Promise<import("..").Batch | number[]>;
|
|
30
30
|
getPageHistory(locale: string, guid: string, pageID: number, take?: number, skip?: number): Promise<PageHistory>;
|
|
31
31
|
getPageComments(locale: string, guid: string, pageID: number, take?: number, skip?: number): Promise<ItemComments>;
|
|
32
32
|
}
|
|
@@ -430,6 +430,10 @@ var PageMethods = /** @class */ (function () {
|
|
|
430
430
|
}); }); })];
|
|
431
431
|
case 2:
|
|
432
432
|
batch = _a.sent();
|
|
433
|
+
// Aaaron May 1 2025 - if the batch has error data, return the batch not just the -1 pageID
|
|
434
|
+
if (batch.errorData) {
|
|
435
|
+
return [2 /*return*/, batch];
|
|
436
|
+
}
|
|
433
437
|
pageIDs_7 = [];
|
|
434
438
|
batch.items.forEach(function (element) { return pageIDs_7.push(element.itemID); });
|
|
435
439
|
return [2 /*return*/, pageIDs_7];
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare class TokenResponseData {
|
|
2
|
+
partitionKey: string;
|
|
3
|
+
rowKey: string;
|
|
4
|
+
access_token: string;
|
|
5
|
+
refresh_token: string;
|
|
6
|
+
token_type?: string;
|
|
7
|
+
expires_in?: number;
|
|
8
|
+
timestamp?: Date;
|
|
9
|
+
etag?: string;
|
|
10
|
+
constructor(partitionKey: string, rowKey: string);
|
|
11
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TokenResponseData = void 0;
|
|
4
|
+
var TokenResponseData = /** @class */ (function () {
|
|
5
|
+
function TokenResponseData(partitionKey, rowKey) {
|
|
6
|
+
this.access_token = '';
|
|
7
|
+
this.refresh_token = '';
|
|
8
|
+
this.partitionKey = partitionKey;
|
|
9
|
+
this.rowKey = rowKey;
|
|
10
|
+
}
|
|
11
|
+
return TokenResponseData;
|
|
12
|
+
}());
|
|
13
|
+
exports.TokenResponseData = TokenResponseData;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
12
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
|
13
|
+
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
14
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
15
|
+
function step(op) {
|
|
16
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
17
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
18
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
19
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
20
|
+
switch (op[0]) {
|
|
21
|
+
case 0: case 1: t = op; break;
|
|
22
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
23
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
24
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
25
|
+
default:
|
|
26
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
27
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
28
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
29
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
30
|
+
if (t[2]) _.ops.pop();
|
|
31
|
+
_.trys.pop(); continue;
|
|
32
|
+
}
|
|
33
|
+
op = body.call(thisArg, _);
|
|
34
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
35
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
var authHelper_1 = require("./authHelper"); // Import the new helper
|
|
40
|
+
describe('Instance Methods', function () {
|
|
41
|
+
var apiClient;
|
|
42
|
+
var guid;
|
|
43
|
+
// Use jest.setTimeout for potentially longer async setup
|
|
44
|
+
jest.setTimeout(30000); // 30 seconds timeout for auth
|
|
45
|
+
beforeAll(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
46
|
+
var authResult, error_1;
|
|
47
|
+
return __generator(this, function (_a) {
|
|
48
|
+
switch (_a.label) {
|
|
49
|
+
case 0:
|
|
50
|
+
_a.trys.push([0, 2, , 3]);
|
|
51
|
+
console.log("Setting up Instance Methods tests...");
|
|
52
|
+
return [4 /*yield*/, (0, authHelper_1.getApiClientWithAuth)()];
|
|
53
|
+
case 1:
|
|
54
|
+
authResult = _a.sent();
|
|
55
|
+
apiClient = authResult.apiClient;
|
|
56
|
+
guid = authResult.guid; // Get guid from the helper result
|
|
57
|
+
if (!apiClient || !guid) {
|
|
58
|
+
throw new Error("Failed to initialize authenticated API client for tests.");
|
|
59
|
+
}
|
|
60
|
+
console.log("Authenticated client ready for Instance Methods tests.");
|
|
61
|
+
return [3 /*break*/, 3];
|
|
62
|
+
case 2:
|
|
63
|
+
error_1 = _a.sent();
|
|
64
|
+
console.error("FATAL: Error during test setup (beforeAll):", error_1);
|
|
65
|
+
// Rethrow or process.exit to prevent tests running without auth
|
|
66
|
+
throw error_1;
|
|
67
|
+
case 3: return [2 /*return*/];
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}); });
|
|
71
|
+
it('should get locales for the instance', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
72
|
+
var locales, error_2;
|
|
73
|
+
return __generator(this, function (_a) {
|
|
74
|
+
switch (_a.label) {
|
|
75
|
+
case 0:
|
|
76
|
+
expect(apiClient.instanceMethods).toBeDefined();
|
|
77
|
+
_a.label = 1;
|
|
78
|
+
case 1:
|
|
79
|
+
_a.trys.push([1, 3, , 4]);
|
|
80
|
+
return [4 /*yield*/, apiClient.instanceMethods.getLocales(guid)];
|
|
81
|
+
case 2:
|
|
82
|
+
locales = _a.sent();
|
|
83
|
+
expect(locales).toBeDefined();
|
|
84
|
+
expect(Array.isArray(locales)).toBe(true);
|
|
85
|
+
expect(locales.length).toBeGreaterThan(0);
|
|
86
|
+
// Check the type of the first locale code
|
|
87
|
+
expect(typeof locales[0]).toBe('string');
|
|
88
|
+
console.log("Found ".concat(locales.length, " locales. First locale code: ").concat(locales[0]));
|
|
89
|
+
return [3 /*break*/, 4];
|
|
90
|
+
case 3:
|
|
91
|
+
error_2 = _a.sent();
|
|
92
|
+
console.error("Error fetching locales:", error_2);
|
|
93
|
+
throw error_2; // Re-throw to fail the test
|
|
94
|
+
case 4: return [2 /*return*/];
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}); });
|
|
98
|
+
// Add more tests for other instanceMethods here
|
|
99
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
12
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
|
13
|
+
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
14
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
15
|
+
function step(op) {
|
|
16
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
17
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
18
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
19
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
20
|
+
switch (op[0]) {
|
|
21
|
+
case 0: case 1: t = op; break;
|
|
22
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
23
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
24
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
25
|
+
default:
|
|
26
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
27
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
28
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
29
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
30
|
+
if (t[2]) _.ops.pop();
|
|
31
|
+
_.trys.pop(); continue;
|
|
32
|
+
}
|
|
33
|
+
op = body.call(thisArg, _);
|
|
34
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
35
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
var authHelper_1 = require("./authHelper");
|
|
40
|
+
var apiClient_1 = require("../apiClient");
|
|
41
|
+
describe('Authentication Helper', function () {
|
|
42
|
+
// Increase timeout for auth process
|
|
43
|
+
jest.setTimeout(30000);
|
|
44
|
+
it('should get an authenticated API client without errors', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
45
|
+
var _a, apiClient, guid, error_1;
|
|
46
|
+
var _b;
|
|
47
|
+
return __generator(this, function (_c) {
|
|
48
|
+
switch (_c.label) {
|
|
49
|
+
case 0:
|
|
50
|
+
_c.trys.push([0, 2, , 3]);
|
|
51
|
+
console.log("Testing getApiClientWithAuth...");
|
|
52
|
+
return [4 /*yield*/, (0, authHelper_1.getApiClientWithAuth)()];
|
|
53
|
+
case 1:
|
|
54
|
+
_a = _c.sent(), apiClient = _a.apiClient, guid = _a.guid;
|
|
55
|
+
expect(apiClient).toBeDefined();
|
|
56
|
+
expect(apiClient).toBeInstanceOf(apiClient_1.ApiClient);
|
|
57
|
+
expect(guid).toBeDefined();
|
|
58
|
+
expect(typeof guid).toBe('string');
|
|
59
|
+
expect(guid.length).toBeGreaterThan(0);
|
|
60
|
+
// Check that the client has options and a token/baseUrl from the auth helper
|
|
61
|
+
expect(apiClient._options).toBeDefined();
|
|
62
|
+
expect(apiClient._options.token).toBeDefined();
|
|
63
|
+
expect(apiClient._options.token.length).toBeGreaterThan(0);
|
|
64
|
+
expect(apiClient._options.baseUrl).toBeDefined();
|
|
65
|
+
expect((_b = apiClient._options.baseUrl) === null || _b === void 0 ? void 0 : _b.startsWith('http')).toBe(true);
|
|
66
|
+
console.log("Successfully obtained authenticated client via helper for GUID: ".concat(guid));
|
|
67
|
+
console.log("Token starts with: ".concat(apiClient._options.token.substring(0, 10), "..."));
|
|
68
|
+
console.log("Base URL: ".concat(apiClient._options.baseUrl));
|
|
69
|
+
return [3 /*break*/, 3];
|
|
70
|
+
case 2:
|
|
71
|
+
error_1 = _c.sent();
|
|
72
|
+
console.error("Error testing getApiClientWithAuth:", error_1);
|
|
73
|
+
throw error_1; // Fail the test if auth helper fails
|
|
74
|
+
case 3: return [2 /*return*/];
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}); });
|
|
78
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Agility CMS Test Environment Variables
|
|
2
|
+
# Copy this file to .env and fill in your actual values.
|
|
3
|
+
|
|
4
|
+
# Required for API calls
|
|
5
|
+
AGILITY_GUID=
|
|
6
|
+
|
|
7
|
+
# Required for Authentication via Azure Table Storage Refresh Token Flow
|
|
8
|
+
STORAGE_ACCOUNT=
|
|
9
|
+
STORAGE_ACCOUNT_KEY=
|
|
10
|
+
STORAGE_ACCOUNT_TABLE=
|
|
11
|
+
AGILITY_MANAGEMENT_API_BASE_URL= # e.g., https://mgmt.aglty.io or https://mgmt-dev.aglty.io
|
|
12
|
+
TEST_ENV=prod # Optional: set to 'dev' if using dev refresh token rowKey, otherwise defaults to 'prod' rowKey
|