yoto-nodejs-client 0.0.1
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/LICENSE +21 -0
- package/README.md +736 -0
- package/bin/auth.d.ts +3 -0
- package/bin/auth.d.ts.map +1 -0
- package/bin/auth.js +130 -0
- package/bin/content.d.ts +3 -0
- package/bin/content.d.ts.map +1 -0
- package/bin/content.js +117 -0
- package/bin/devices.d.ts +3 -0
- package/bin/devices.d.ts.map +1 -0
- package/bin/devices.js +239 -0
- package/bin/groups.d.ts +3 -0
- package/bin/groups.d.ts.map +1 -0
- package/bin/groups.js +80 -0
- package/bin/icons.d.ts +3 -0
- package/bin/icons.d.ts.map +1 -0
- package/bin/icons.js +100 -0
- package/bin/lib/cli-helpers.d.ts +21 -0
- package/bin/lib/cli-helpers.d.ts.map +1 -0
- package/bin/lib/cli-helpers.js +140 -0
- package/bin/lib/token-helpers.d.ts +14 -0
- package/bin/lib/token-helpers.d.ts.map +1 -0
- package/bin/lib/token-helpers.js +151 -0
- package/bin/refresh-token.d.ts +3 -0
- package/bin/refresh-token.d.ts.map +1 -0
- package/bin/refresh-token.js +168 -0
- package/bin/token-info.d.ts +3 -0
- package/bin/token-info.d.ts.map +1 -0
- package/bin/token-info.js +351 -0
- package/index.d.ts +218 -0
- package/index.d.ts.map +1 -0
- package/index.js +689 -0
- package/lib/api-endpoints/auth.d.ts +56 -0
- package/lib/api-endpoints/auth.d.ts.map +1 -0
- package/lib/api-endpoints/auth.js +209 -0
- package/lib/api-endpoints/auth.test.js +27 -0
- package/lib/api-endpoints/constants.d.ts +6 -0
- package/lib/api-endpoints/constants.d.ts.map +1 -0
- package/lib/api-endpoints/constants.js +31 -0
- package/lib/api-endpoints/content.d.ts +275 -0
- package/lib/api-endpoints/content.d.ts.map +1 -0
- package/lib/api-endpoints/content.js +518 -0
- package/lib/api-endpoints/content.test.js +250 -0
- package/lib/api-endpoints/devices.d.ts +202 -0
- package/lib/api-endpoints/devices.d.ts.map +1 -0
- package/lib/api-endpoints/devices.js +404 -0
- package/lib/api-endpoints/devices.test.js +483 -0
- package/lib/api-endpoints/family-library-groups.d.ts +75 -0
- package/lib/api-endpoints/family-library-groups.d.ts.map +1 -0
- package/lib/api-endpoints/family-library-groups.js +247 -0
- package/lib/api-endpoints/family-library-groups.test.js +272 -0
- package/lib/api-endpoints/family.d.ts +39 -0
- package/lib/api-endpoints/family.d.ts.map +1 -0
- package/lib/api-endpoints/family.js +166 -0
- package/lib/api-endpoints/family.test.js +184 -0
- package/lib/api-endpoints/helpers.d.ts +29 -0
- package/lib/api-endpoints/helpers.d.ts.map +1 -0
- package/lib/api-endpoints/helpers.js +104 -0
- package/lib/api-endpoints/icons.d.ts +62 -0
- package/lib/api-endpoints/icons.d.ts.map +1 -0
- package/lib/api-endpoints/icons.js +201 -0
- package/lib/api-endpoints/icons.test.js +118 -0
- package/lib/api-endpoints/media.d.ts +37 -0
- package/lib/api-endpoints/media.d.ts.map +1 -0
- package/lib/api-endpoints/media.js +155 -0
- package/lib/api-endpoints/test-helpers.d.ts +7 -0
- package/lib/api-endpoints/test-helpers.d.ts.map +1 -0
- package/lib/api-endpoints/test-helpers.js +64 -0
- package/lib/mqtt/client.d.ts +124 -0
- package/lib/mqtt/client.d.ts.map +1 -0
- package/lib/mqtt/client.js +558 -0
- package/lib/mqtt/commands.d.ts +69 -0
- package/lib/mqtt/commands.d.ts.map +1 -0
- package/lib/mqtt/commands.js +238 -0
- package/lib/mqtt/factory.d.ts +12 -0
- package/lib/mqtt/factory.d.ts.map +1 -0
- package/lib/mqtt/factory.js +107 -0
- package/lib/mqtt/index.d.ts +5 -0
- package/lib/mqtt/index.d.ts.map +1 -0
- package/lib/mqtt/index.js +81 -0
- package/lib/mqtt/mqtt.test.js +168 -0
- package/lib/mqtt/topics.d.ts +34 -0
- package/lib/mqtt/topics.d.ts.map +1 -0
- package/lib/mqtt/topics.js +295 -0
- package/lib/pkg.cjs +3 -0
- package/lib/pkg.d.cts +70 -0
- package/lib/pkg.d.cts.map +1 -0
- package/lib/token.d.ts +29 -0
- package/lib/token.d.ts.map +1 -0
- package/lib/token.js +240 -0
- package/package.json +91 -0
- package/yoto.png +0 -0
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @import { RequestOptions } from './helpers.js'
|
|
3
|
+
*/
|
|
4
|
+
import { request } from 'undici'
|
|
5
|
+
import { defaultAuthHeaders, handleBadResponse, mergeRequestOptions } from './helpers.js'
|
|
6
|
+
import { YOTO_API_URL } from './constants.js'
|
|
7
|
+
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// Family Library Groups: Endpoints for managing family library card groups
|
|
10
|
+
// ============================================================================
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @see https://yoto.dev/api/getgroups/
|
|
14
|
+
* @typedef {Object} YotoGroupsResponse
|
|
15
|
+
* @property {YotoGroup[]} groups - Array of family library groups
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @see https://yoto.dev/api/getgroups/
|
|
20
|
+
* @typedef {Object} YotoGroup
|
|
21
|
+
* @property {string} id - Group identifier
|
|
22
|
+
* @property {string} name - Group name (e.g., "My Favourites")
|
|
23
|
+
* @property {string} familyId - Associated family ID
|
|
24
|
+
* @property {string} imageId - ID for the group image (can be uploaded family image hash or preset like "fp-cards")
|
|
25
|
+
* @property {string} imageUrl - CDN URL to the group image
|
|
26
|
+
* @property {YotoGroupItem[]} items - Array of content items in the group
|
|
27
|
+
* @property {any[]} cards - Array of card objects
|
|
28
|
+
* @property {string} createdAt - ISO 8601 timestamp when group was created
|
|
29
|
+
* @property {string} lastModifiedAt - ISO 8601 timestamp when group was last updated
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @see https://yoto.dev/api/getgroups/
|
|
34
|
+
* @typedef {Object} YotoGroupItem
|
|
35
|
+
* @property {string} contentId - ID of the card content
|
|
36
|
+
* @property {string} addedAt - ISO 8601 timestamp when item was added to group
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Retrieves all family library groups for the authenticated user's family.
|
|
41
|
+
* Returns an empty array if no groups exist.
|
|
42
|
+
* @see https://yoto.dev/api/getgroups/
|
|
43
|
+
* @param {object} options
|
|
44
|
+
* @param {string} options.accessToken The API token to request with
|
|
45
|
+
* @param {string} [options.userAgent] Optional user agent string
|
|
46
|
+
* @param {RequestOptions} [options.requestOptions] Additional undici request options
|
|
47
|
+
* @return {Promise<YotoGroup[]>} Array of family library groups
|
|
48
|
+
* @example
|
|
49
|
+
* import { getGroups } from 'yoto-nodejs-client'
|
|
50
|
+
*
|
|
51
|
+
* const groups = await getGroups({
|
|
52
|
+
* accessToken
|
|
53
|
+
* })
|
|
54
|
+
*
|
|
55
|
+
* console.log('Groups:', groups.length)
|
|
56
|
+
* groups.forEach(group => {
|
|
57
|
+
* console.log(`${group.name}: ${group.items.length} items`)
|
|
58
|
+
* })
|
|
59
|
+
*/
|
|
60
|
+
export async function getGroups ({
|
|
61
|
+
accessToken,
|
|
62
|
+
userAgent,
|
|
63
|
+
requestOptions
|
|
64
|
+
}) {
|
|
65
|
+
const requestUrl = new URL('/card/family/library/groups', YOTO_API_URL)
|
|
66
|
+
|
|
67
|
+
const response = await request(requestUrl, mergeRequestOptions({
|
|
68
|
+
method: 'GET',
|
|
69
|
+
headers: defaultAuthHeaders({ accessToken, userAgent })
|
|
70
|
+
}, requestOptions))
|
|
71
|
+
|
|
72
|
+
await handleBadResponse(response)
|
|
73
|
+
|
|
74
|
+
const responseBody = /** @type {YotoGroup[]} */ (await response.body.json())
|
|
75
|
+
return responseBody
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* @see https://yoto.dev/api/createagroup/
|
|
80
|
+
* @typedef {Object} YotoCreateGroupRequest
|
|
81
|
+
* @property {string} name - Group name (max 100 characters, UTF-8 supported)
|
|
82
|
+
* @property {string} imageId - Image ID (preset like "fp-cards" or uploaded image hash)
|
|
83
|
+
* @property {YotoGroupItemInput[]} items - Array of content items (can be empty, order preserved)
|
|
84
|
+
*/
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* @see https://yoto.dev/api/createagroup/
|
|
88
|
+
* @typedef {Object} YotoGroupItemInput
|
|
89
|
+
* @property {string} contentId - ID of the card content to add to group
|
|
90
|
+
*/
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Creates a new group in the family library.
|
|
94
|
+
* Max 20 groups per family. ContentIds must be in family's library (invalid ones filtered out).
|
|
95
|
+
* @see https://yoto.dev/api/createagroup/
|
|
96
|
+
* @param {object} options
|
|
97
|
+
* @param {string} options.token The API token to request with
|
|
98
|
+
* @param {YotoCreateGroupRequest} options.group The group data to create
|
|
99
|
+
* @param {string} [options.userAgent] Optional user agent string
|
|
100
|
+
* @param {RequestOptions} [options.requestOptions] Additional undici request options
|
|
101
|
+
* @return {Promise<YotoGroup>} The created group with populated cards array
|
|
102
|
+
* @example
|
|
103
|
+
* import { createGroup } from 'yoto-nodejs-client'
|
|
104
|
+
*
|
|
105
|
+
* const group = await createGroup({
|
|
106
|
+
* token: accessToken,
|
|
107
|
+
* group: {
|
|
108
|
+
* name: 'My Favourites',
|
|
109
|
+
* imageId: 'fp-cards',
|
|
110
|
+
* items: [
|
|
111
|
+
* { contentId: '37KwQ' }
|
|
112
|
+
* ]
|
|
113
|
+
* }
|
|
114
|
+
* })
|
|
115
|
+
*/
|
|
116
|
+
export async function createGroup ({
|
|
117
|
+
token,
|
|
118
|
+
userAgent,
|
|
119
|
+
group,
|
|
120
|
+
requestOptions
|
|
121
|
+
}) {
|
|
122
|
+
const requestUrl = new URL('/card/family/library/groups', YOTO_API_URL)
|
|
123
|
+
|
|
124
|
+
const response = await request(requestUrl, mergeRequestOptions({
|
|
125
|
+
method: 'POST',
|
|
126
|
+
headers: {
|
|
127
|
+
...defaultAuthHeaders({ accessToken: token, userAgent }),
|
|
128
|
+
'Content-Type': 'application/json'
|
|
129
|
+
},
|
|
130
|
+
body: JSON.stringify(group)
|
|
131
|
+
}, requestOptions))
|
|
132
|
+
|
|
133
|
+
await handleBadResponse(response)
|
|
134
|
+
|
|
135
|
+
const responseBody = /** @type {YotoGroup} */ (await response.body.json())
|
|
136
|
+
return responseBody
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Retrieves a specific group by ID.
|
|
141
|
+
* Returns 404 if group doesn't exist or belongs to another family.
|
|
142
|
+
* @see https://yoto.dev/api/getagroup/
|
|
143
|
+
* @param {object} options
|
|
144
|
+
* @param {string} options.accessToken The API token to request with
|
|
145
|
+
* @param {string} options.groupId The group ID to retrieve
|
|
146
|
+
* @param {string} [options.userAgent] Optional user agent string
|
|
147
|
+
* @param {RequestOptions} [options.requestOptions] Additional undici request options
|
|
148
|
+
* @return {Promise<YotoGroup>} The requested group with populated cards array
|
|
149
|
+
*/
|
|
150
|
+
export async function getGroup ({
|
|
151
|
+
accessToken,
|
|
152
|
+
userAgent,
|
|
153
|
+
groupId,
|
|
154
|
+
requestOptions
|
|
155
|
+
}) {
|
|
156
|
+
const requestUrl = new URL(`/card/family/library/groups/${groupId}`, YOTO_API_URL)
|
|
157
|
+
|
|
158
|
+
const response = await request(requestUrl, mergeRequestOptions({
|
|
159
|
+
method: 'GET',
|
|
160
|
+
headers: defaultAuthHeaders({ accessToken, userAgent })
|
|
161
|
+
}, requestOptions))
|
|
162
|
+
|
|
163
|
+
await handleBadResponse(response, { groupId })
|
|
164
|
+
|
|
165
|
+
const responseBody = /** @type {YotoGroup} */ (await response.body.json())
|
|
166
|
+
return responseBody
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* @see https://yoto.dev/api/updateagroup/
|
|
171
|
+
* @typedef {Object} YotoUpdateGroupRequest
|
|
172
|
+
* @property {string} name - Group name (max 100 characters, UTF-8 supported)
|
|
173
|
+
* @property {string} imageId - Image ID (preset like "fp-cards" or uploaded image hash)
|
|
174
|
+
* @property {YotoGroupItemInput[]} items - Array of content items (replaces entire array)
|
|
175
|
+
*/
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Updates an existing group.
|
|
179
|
+
* Can only update groups owned by family. Returns 404 if doesn't exist or owned by another family.
|
|
180
|
+
* @see https://yoto.dev/api/updateagroup/
|
|
181
|
+
* @param {object} options
|
|
182
|
+
* @param {string} options.accessToken The API token to request with
|
|
183
|
+
* @param {string} options.groupId The group ID to update
|
|
184
|
+
* @param {YotoUpdateGroupRequest} options.group The updated group data
|
|
185
|
+
* @param {string} [options.userAgent] Optional user agent string
|
|
186
|
+
* @param {RequestOptions} [options.requestOptions] Additional undici request options
|
|
187
|
+
* @return {Promise<YotoGroup>} The updated group with populated cards array
|
|
188
|
+
*/
|
|
189
|
+
export async function updateGroup ({
|
|
190
|
+
accessToken,
|
|
191
|
+
userAgent,
|
|
192
|
+
groupId,
|
|
193
|
+
group,
|
|
194
|
+
requestOptions
|
|
195
|
+
}) {
|
|
196
|
+
const requestUrl = new URL(`/card/family/library/groups/${groupId}`, YOTO_API_URL)
|
|
197
|
+
|
|
198
|
+
const response = await request(requestUrl, mergeRequestOptions({
|
|
199
|
+
method: 'PUT',
|
|
200
|
+
headers: {
|
|
201
|
+
...defaultAuthHeaders({ accessToken, userAgent }),
|
|
202
|
+
'Content-Type': 'application/json'
|
|
203
|
+
},
|
|
204
|
+
body: JSON.stringify(group)
|
|
205
|
+
}, requestOptions))
|
|
206
|
+
|
|
207
|
+
await handleBadResponse(response, { groupId })
|
|
208
|
+
|
|
209
|
+
const responseBody = /** @type {YotoGroup} */ (await response.body.json())
|
|
210
|
+
return responseBody
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* @see https://yoto.dev/api/deleteagroup/
|
|
215
|
+
* @typedef {Object} YotoDeleteGroupResponse
|
|
216
|
+
* @property {string} id - The ID of the deleted group
|
|
217
|
+
*/
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Deletes a group permanently (hard delete, cannot be recovered).
|
|
221
|
+
* Content remains in family library. Returns 404 if doesn't exist or owned by another family.
|
|
222
|
+
* @see https://yoto.dev/api/deleteagroup/
|
|
223
|
+
* @param {object} options
|
|
224
|
+
* @param {string} options.accessToken The API token to request with
|
|
225
|
+
* @param {string} options.groupId The group ID to delete
|
|
226
|
+
* @param {string} [options.userAgent] Optional user agent string
|
|
227
|
+
* @param {RequestOptions} [options.requestOptions] Additional undici request options
|
|
228
|
+
* @return {Promise<YotoDeleteGroupResponse>} Confirmation with deleted group ID
|
|
229
|
+
*/
|
|
230
|
+
export async function deleteGroup ({
|
|
231
|
+
accessToken,
|
|
232
|
+
userAgent,
|
|
233
|
+
groupId,
|
|
234
|
+
requestOptions
|
|
235
|
+
}) {
|
|
236
|
+
const requestUrl = new URL(`/card/family/library/groups/${groupId}`, YOTO_API_URL)
|
|
237
|
+
|
|
238
|
+
const response = await request(requestUrl, mergeRequestOptions({
|
|
239
|
+
method: 'DELETE',
|
|
240
|
+
headers: defaultAuthHeaders({ accessToken, userAgent })
|
|
241
|
+
}, requestOptions))
|
|
242
|
+
|
|
243
|
+
await handleBadResponse(response, { groupId })
|
|
244
|
+
|
|
245
|
+
const responseBody = /** @type {YotoDeleteGroupResponse} */ (await response.body.json())
|
|
246
|
+
return responseBody
|
|
247
|
+
}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import test from 'node:test'
|
|
2
|
+
import assert from 'node:assert'
|
|
3
|
+
import { getGroups, createGroup, getGroup, updateGroup, deleteGroup } from './family-library-groups.js'
|
|
4
|
+
import { YotoAPIError } from './helpers.js'
|
|
5
|
+
import { loadTestTokens, logResponse } from './test-helpers.js'
|
|
6
|
+
|
|
7
|
+
const { accessToken } = loadTestTokens()
|
|
8
|
+
|
|
9
|
+
test('getGroups', async (t) => {
|
|
10
|
+
await t.test('should fetch family library groups', async () => {
|
|
11
|
+
const response = await getGroups({
|
|
12
|
+
accessToken
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
// Log response for type verification and documentation
|
|
16
|
+
logResponse('GET /card/family/library/groups', response)
|
|
17
|
+
|
|
18
|
+
// Validate response structure matches YotoGroup[]
|
|
19
|
+
assert.ok(response, 'Response should exist')
|
|
20
|
+
assert.ok(Array.isArray(response), 'Response should be an array')
|
|
21
|
+
|
|
22
|
+
// Note: User may not have any groups, so we only validate structure if groups exist
|
|
23
|
+
if (response.length > 0) {
|
|
24
|
+
const group = response[0]
|
|
25
|
+
assert.ok(group, 'Group should exist')
|
|
26
|
+
assert.ok(typeof group.id === 'string', 'Group should have id string')
|
|
27
|
+
assert.ok(typeof group.name === 'string', 'Group should have name string')
|
|
28
|
+
assert.ok(typeof group.familyId === 'string', 'Group should have familyId string')
|
|
29
|
+
assert.ok(typeof group.imageId === 'string', 'Group should have imageId string')
|
|
30
|
+
assert.ok(typeof group.imageUrl === 'string', 'Group should have imageUrl string')
|
|
31
|
+
assert.ok(Array.isArray(group.items), 'Group should have items array')
|
|
32
|
+
assert.ok(Array.isArray(group.cards), 'Group should have cards array')
|
|
33
|
+
assert.ok(typeof group.createdAt === 'string', 'Group should have createdAt string')
|
|
34
|
+
assert.ok(typeof group.lastModifiedAt === 'string', 'Group should have lastModifiedAt string')
|
|
35
|
+
|
|
36
|
+
// Validate items structure if items exist
|
|
37
|
+
if (group.items.length > 0) {
|
|
38
|
+
const item = group.items[0]
|
|
39
|
+
assert.ok(item, 'Item should exist')
|
|
40
|
+
assert.ok(typeof item.contentId === 'string', 'Item should have contentId string')
|
|
41
|
+
assert.ok(typeof item.addedAt === 'string', 'Item should have addedAt string')
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
await t.test('should fail with invalid token', async () => {
|
|
47
|
+
await assert.rejects(
|
|
48
|
+
async () => {
|
|
49
|
+
await getGroups({
|
|
50
|
+
accessToken: 'invalid-token'
|
|
51
|
+
})
|
|
52
|
+
},
|
|
53
|
+
(err) => {
|
|
54
|
+
assert.ok(err instanceof YotoAPIError, 'Should throw YotoAPIError')
|
|
55
|
+
assert.ok(err.statusCode === 401 || err.statusCode === 403, 'Should return 401 or 403 for invalid token')
|
|
56
|
+
assert.ok(err.body, 'Error should have body')
|
|
57
|
+
return true
|
|
58
|
+
}
|
|
59
|
+
)
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test('CRUD operations - full lifecycle', async (t) => {
|
|
64
|
+
/** @type {string[]} */
|
|
65
|
+
let createdGroupIds = []
|
|
66
|
+
/** @type {string} */
|
|
67
|
+
let group1Id
|
|
68
|
+
/** @type {string} */
|
|
69
|
+
let group2Id
|
|
70
|
+
|
|
71
|
+
// Cleanup function to run after all tests
|
|
72
|
+
t.after(async () => {
|
|
73
|
+
console.log('\n🧹 Cleaning up test groups...')
|
|
74
|
+
for (const groupId of createdGroupIds) {
|
|
75
|
+
try {
|
|
76
|
+
await deleteGroup({ accessToken, groupId })
|
|
77
|
+
console.log(` ✓ Deleted group: ${groupId}`)
|
|
78
|
+
} catch (err) {
|
|
79
|
+
console.log(` ⚠️ Failed to delete group ${groupId}:`, /** @type {Error} */(err).message)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
await t.test('should create first group', async () => {
|
|
85
|
+
const group = await createGroup({
|
|
86
|
+
token: accessToken,
|
|
87
|
+
group: {
|
|
88
|
+
name: 'Test Group 1',
|
|
89
|
+
imageId: 'fp-cards',
|
|
90
|
+
items: []
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
logResponse('POST /card/family/library/groups (group 1)', group)
|
|
95
|
+
|
|
96
|
+
// Validate response structure
|
|
97
|
+
assert.ok(group, 'Response should exist')
|
|
98
|
+
assert.ok(typeof group.id === 'string', 'Group should have id string')
|
|
99
|
+
assert.strictEqual(group.name, 'Test Group 1', 'Group name should match')
|
|
100
|
+
assert.strictEqual(group.imageId, 'fp-cards', 'Image ID should match')
|
|
101
|
+
assert.ok(Array.isArray(group.items), 'Group should have items array')
|
|
102
|
+
assert.strictEqual(group.items.length, 0, 'Items should be empty')
|
|
103
|
+
assert.ok(Array.isArray(group.cards), 'Group should have cards array')
|
|
104
|
+
assert.ok(typeof group.familyId === 'string', 'Group should have familyId')
|
|
105
|
+
assert.ok(typeof group.createdAt === 'string', 'Group should have createdAt')
|
|
106
|
+
assert.ok(typeof group.lastModifiedAt === 'string', 'Group should have lastModifiedAt')
|
|
107
|
+
|
|
108
|
+
group1Id = group.id
|
|
109
|
+
createdGroupIds.push(group.id)
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
await t.test('should create second group', async () => {
|
|
113
|
+
const group = await createGroup({
|
|
114
|
+
token: accessToken,
|
|
115
|
+
group: {
|
|
116
|
+
name: 'Test Group 2',
|
|
117
|
+
imageId: 'fp-cards',
|
|
118
|
+
items: []
|
|
119
|
+
}
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
logResponse('POST /card/family/library/groups (group 2)', group)
|
|
123
|
+
|
|
124
|
+
// Validate response structure
|
|
125
|
+
assert.ok(group, 'Response should exist')
|
|
126
|
+
assert.ok(typeof group.id === 'string', 'Group should have id string')
|
|
127
|
+
assert.strictEqual(group.name, 'Test Group 2', 'Group name should match')
|
|
128
|
+
assert.notStrictEqual(group.id, group1Id, 'Group IDs should be different')
|
|
129
|
+
|
|
130
|
+
group2Id = group.id
|
|
131
|
+
createdGroupIds.push(group.id)
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
await t.test('should list groups and find both created groups', async () => {
|
|
135
|
+
const groups = await getGroups({
|
|
136
|
+
accessToken
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
logResponse('GET /card/family/library/groups (after creating 2)', groups)
|
|
140
|
+
|
|
141
|
+
assert.ok(Array.isArray(groups), 'Response should be an array')
|
|
142
|
+
assert.ok(groups.length >= 2, 'Should have at least 2 groups')
|
|
143
|
+
|
|
144
|
+
const foundGroup1 = groups.find(g => g.id === group1Id)
|
|
145
|
+
const foundGroup2 = groups.find(g => g.id === group2Id)
|
|
146
|
+
|
|
147
|
+
assert.ok(foundGroup1, 'Should find first created group in list')
|
|
148
|
+
assert.strictEqual(foundGroup1.name, 'Test Group 1', 'First group name should match')
|
|
149
|
+
|
|
150
|
+
assert.ok(foundGroup2, 'Should find second created group in list')
|
|
151
|
+
assert.strictEqual(foundGroup2.name, 'Test Group 2', 'Second group name should match')
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
await t.test('should get first group by ID', async () => {
|
|
155
|
+
const group = await getGroup({
|
|
156
|
+
accessToken,
|
|
157
|
+
groupId: group1Id
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
logResponse(`GET /card/family/library/groups/${group1Id}`, group)
|
|
161
|
+
|
|
162
|
+
assert.ok(group, 'Response should exist')
|
|
163
|
+
assert.strictEqual(group.id, group1Id, 'Group ID should match')
|
|
164
|
+
assert.strictEqual(group.name, 'Test Group 1', 'Group name should match')
|
|
165
|
+
assert.strictEqual(group.imageId, 'fp-cards', 'Image ID should match')
|
|
166
|
+
assert.ok(Array.isArray(group.items), 'Group should have items array')
|
|
167
|
+
assert.ok(Array.isArray(group.cards), 'Group should have cards array')
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
await t.test('should get second group by ID', async () => {
|
|
171
|
+
const group = await getGroup({
|
|
172
|
+
accessToken,
|
|
173
|
+
groupId: group2Id
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
logResponse(`GET /card/family/library/groups/${group2Id}`, group)
|
|
177
|
+
|
|
178
|
+
assert.ok(group, 'Response should exist')
|
|
179
|
+
assert.strictEqual(group.id, group2Id, 'Group ID should match')
|
|
180
|
+
assert.strictEqual(group.name, 'Test Group 2', 'Group name should match')
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
await t.test('should update first group', async () => {
|
|
184
|
+
const updatedGroup = await updateGroup({
|
|
185
|
+
accessToken,
|
|
186
|
+
groupId: group1Id,
|
|
187
|
+
group: {
|
|
188
|
+
name: 'Test Group 1 - Updated',
|
|
189
|
+
imageId: 'fp-cards',
|
|
190
|
+
items: []
|
|
191
|
+
}
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
logResponse(`PUT /card/family/library/groups/${group1Id}`, updatedGroup)
|
|
195
|
+
|
|
196
|
+
assert.ok(updatedGroup, 'Response should exist')
|
|
197
|
+
assert.strictEqual(updatedGroup.id, group1Id, 'Group ID should remain the same')
|
|
198
|
+
assert.strictEqual(updatedGroup.name, 'Test Group 1 - Updated', 'Group name should be updated')
|
|
199
|
+
assert.ok(typeof updatedGroup.lastModifiedAt === 'string', 'Should have lastModifiedAt')
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
await t.test('should get updated group and confirm changes', async () => {
|
|
203
|
+
const group = await getGroup({
|
|
204
|
+
accessToken,
|
|
205
|
+
groupId: group1Id
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
logResponse(`GET /card/family/library/groups/${group1Id} (after update)`, group)
|
|
209
|
+
|
|
210
|
+
assert.strictEqual(group.name, 'Test Group 1 - Updated', 'Updated name should persist')
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
await t.test('should delete first group', async () => {
|
|
214
|
+
const response = await deleteGroup({
|
|
215
|
+
accessToken,
|
|
216
|
+
groupId: group1Id
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
logResponse(`DELETE /card/family/library/groups/${group1Id}`, response)
|
|
220
|
+
|
|
221
|
+
assert.ok(response, 'Response should exist')
|
|
222
|
+
assert.strictEqual(response.id, group1Id, 'Deleted group ID should match')
|
|
223
|
+
|
|
224
|
+
// Remove from cleanup list since we already deleted it
|
|
225
|
+
createdGroupIds = createdGroupIds.filter(id => id !== group1Id)
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
await t.test('should delete second group', async () => {
|
|
229
|
+
const response = await deleteGroup({
|
|
230
|
+
accessToken,
|
|
231
|
+
groupId: group2Id
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
logResponse(`DELETE /card/family/library/groups/${group2Id}`, response)
|
|
235
|
+
|
|
236
|
+
assert.ok(response, 'Response should exist')
|
|
237
|
+
assert.strictEqual(response.id, group2Id, 'Deleted group ID should match')
|
|
238
|
+
|
|
239
|
+
// Remove from cleanup list since we already deleted it
|
|
240
|
+
createdGroupIds = createdGroupIds.filter(id => id !== group2Id)
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
await t.test('should verify groups are deleted from list', async () => {
|
|
244
|
+
const groups = await getGroups({
|
|
245
|
+
accessToken
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
logResponse('GET /card/family/library/groups (after deletion)', groups)
|
|
249
|
+
|
|
250
|
+
const foundGroup1 = groups.find(g => g.id === group1Id)
|
|
251
|
+
const foundGroup2 = groups.find(g => g.id === group2Id)
|
|
252
|
+
|
|
253
|
+
assert.strictEqual(foundGroup1, undefined, 'First group should not be in list')
|
|
254
|
+
assert.strictEqual(foundGroup2, undefined, 'Second group should not be in list')
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
await t.test('should return 404 when getting deleted group', async () => {
|
|
258
|
+
await assert.rejects(
|
|
259
|
+
async () => {
|
|
260
|
+
await getGroup({
|
|
261
|
+
accessToken,
|
|
262
|
+
groupId: group1Id
|
|
263
|
+
})
|
|
264
|
+
},
|
|
265
|
+
(err) => {
|
|
266
|
+
assert.ok(err instanceof YotoAPIError, 'Should throw YotoAPIError')
|
|
267
|
+
assert.strictEqual(err.statusCode, 404, 'Should return 404 for deleted group')
|
|
268
|
+
return true
|
|
269
|
+
}
|
|
270
|
+
)
|
|
271
|
+
})
|
|
272
|
+
})
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export function getFamilyImages({ accessToken, userAgent, requestOptions }: {
|
|
2
|
+
accessToken: string;
|
|
3
|
+
userAgent?: string | undefined;
|
|
4
|
+
requestOptions?: ({
|
|
5
|
+
dispatcher?: import("undici").Dispatcher;
|
|
6
|
+
} & Omit<import("undici").Dispatcher.RequestOptions<unknown>, "origin" | "path" | "method"> & Partial<Pick<import("undici").Dispatcher.RequestOptions<null>, "method">>) | undefined;
|
|
7
|
+
}): Promise<YotoFamilyImagesResponse>;
|
|
8
|
+
export function getAFamilyImage({ accessToken, userAgent, imageId, size, requestOptions }: {
|
|
9
|
+
accessToken: string;
|
|
10
|
+
imageId: string;
|
|
11
|
+
size: "640x480" | "320x320";
|
|
12
|
+
userAgent?: string | undefined;
|
|
13
|
+
requestOptions?: ({
|
|
14
|
+
dispatcher?: import("undici").Dispatcher;
|
|
15
|
+
} & Omit<import("undici").Dispatcher.RequestOptions<unknown>, "origin" | "path" | "method"> & Partial<Pick<import("undici").Dispatcher.RequestOptions<null>, "method">>) | undefined;
|
|
16
|
+
}): Promise<YotoFamilyImageResponse>;
|
|
17
|
+
export function uploadAFamilyImage({ accessToken, userAgent, imageData, requestOptions }: {
|
|
18
|
+
accessToken: string;
|
|
19
|
+
imageData: Buffer;
|
|
20
|
+
userAgent?: string | undefined;
|
|
21
|
+
requestOptions?: ({
|
|
22
|
+
dispatcher?: import("undici").Dispatcher;
|
|
23
|
+
} & Omit<import("undici").Dispatcher.RequestOptions<unknown>, "origin" | "path" | "method"> & Partial<Pick<import("undici").Dispatcher.RequestOptions<null>, "method">>) | undefined;
|
|
24
|
+
}): Promise<YotoUploadFamilyImageResponse>;
|
|
25
|
+
export type YotoFamilyImagesResponse = {
|
|
26
|
+
images: YotoFamilyImage[];
|
|
27
|
+
};
|
|
28
|
+
export type YotoFamilyImage = {
|
|
29
|
+
imageId: string;
|
|
30
|
+
name?: string;
|
|
31
|
+
};
|
|
32
|
+
export type YotoFamilyImageResponse = {
|
|
33
|
+
imageUrl: string;
|
|
34
|
+
};
|
|
35
|
+
export type YotoUploadFamilyImageResponse = {
|
|
36
|
+
imageId: string;
|
|
37
|
+
url: string;
|
|
38
|
+
};
|
|
39
|
+
//# sourceMappingURL=family.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"family.d.ts","sourceRoot":"","sources":["family.js"],"names":[],"mappings":"AAiCA,4EALG;IAAyB,WAAW,EAA3B,MAAM;IACW,SAAS;IACD,cAAc;;;CAChD,GAAS,OAAO,CAAC,wBAAwB,CAAC,CAkB5C;AAoBD,2FAPG;IAAyB,WAAW,EAA3B,MAAM;IACU,OAAO,EAAvB,MAAM;IACyB,IAAI,EAAnC,SAAS,GAAG,SAAS;IACJ,SAAS;IACD,cAAc;;;CAChD,GAAS,OAAO,CAAC,uBAAuB,CAAC,CAsC3C;AAuCD,0FAlBG;IAAyB,WAAW,EAA3B,MAAM;IACU,SAAS,EAAzB,MAAM;IACW,SAAS;IACD,cAAc;;;CAChD,GAAS,OAAO,CAAC,6BAA6B,CAAC,CAmCjD;;YAvJa,eAAe,EAAE;;;aAMjB,MAAM;WACN,MAAM;;;cAiCN,MAAM;;;aAwDN,MAAM;SACN,MAAM"}
|