1
1
import { defineApp } from "@pipedream/types" ;
2
+ import { axios } from "@pipedream/platform" ;
3
+ import {
4
+ BASE_URL ,
5
+ ENDPOINTS ,
6
+ DEFAULT_LIMIT ,
7
+ MAX_LIMIT ,
8
+ SORT_OPTIONS ,
9
+ RATING_SCALE ,
10
+ RETRY_CONFIG ,
11
+ HTTP_STATUS ,
12
+ } from "../common/constants.mjs" ;
13
+ import {
14
+ buildUrl ,
15
+ parseReview ,
16
+ parseBusinessUnit ,
17
+ parseWebhookPayload ,
18
+ validateBusinessUnitId ,
19
+ validateReviewId ,
20
+ formatQueryParams ,
21
+ parseApiError ,
22
+ sleep ,
23
+ } from "../common/utils.mjs" ;
2
24
3
25
export default defineApp ( {
4
26
type : "app" ,
5
27
app : "trustpilot" ,
6
- propDefinitions : { } ,
28
+ propDefinitions : {
29
+ businessUnitId : {
30
+ type : "string" ,
31
+ label : "Business Unit ID" ,
32
+ description : "The unique identifier for your business unit on Trustpilot" ,
33
+ async options ( ) {
34
+ try {
35
+ const businessUnits = await this . searchBusinessUnits ( {
36
+ query : "" ,
37
+ limit : 20 ,
38
+ } ) ;
39
+ return businessUnits . map ( unit => ( {
40
+ label : unit . displayName ,
41
+ value : unit . id ,
42
+ } ) ) ;
43
+ } catch ( error ) {
44
+ console . error ( "Error fetching business units:" , error ) ;
45
+ return [ ] ;
46
+ }
47
+ } ,
48
+ } ,
49
+ reviewId : {
50
+ type : "string" ,
51
+ label : "Review ID" ,
52
+ description : "The unique identifier for a review" ,
53
+ } ,
54
+ stars : {
55
+ type : "integer" ,
56
+ label : "Star Rating" ,
57
+ description : "Filter by star rating (1-5)" ,
58
+ options : RATING_SCALE ,
59
+ optional : true ,
60
+ } ,
61
+ sortBy : {
62
+ type : "string" ,
63
+ label : "Sort By" ,
64
+ description : "How to sort the results" ,
65
+ options : Object . entries ( SORT_OPTIONS ) . map ( ( [ key , value ] ) => ( {
66
+ label : key . replace ( / _ / g, " " ) . toLowerCase ( ) ,
67
+ value,
68
+ } ) ) ,
69
+ optional : true ,
70
+ default : SORT_OPTIONS . CREATED_AT_DESC ,
71
+ } ,
72
+ limit : {
73
+ type : "integer" ,
74
+ label : "Limit" ,
75
+ description : "Maximum number of results to return" ,
76
+ min : 1 ,
77
+ max : MAX_LIMIT ,
78
+ default : DEFAULT_LIMIT ,
79
+ optional : true ,
80
+ } ,
81
+ includeReportedReviews : {
82
+ type : "boolean" ,
83
+ label : "Include Reported Reviews" ,
84
+ description : "Whether to include reviews that have been reported" ,
85
+ default : false ,
86
+ optional : true ,
87
+ } ,
88
+ tags : {
89
+ type : "string[]" ,
90
+ label : "Tags" ,
91
+ description : "Filter reviews by tags" ,
92
+ optional : true ,
93
+ } ,
94
+ language : {
95
+ type : "string" ,
96
+ label : "Language" ,
97
+ description : "Filter reviews by language (ISO 639-1 code)" ,
98
+ optional : true ,
99
+ } ,
100
+ } ,
7
101
methods : {
8
- // this.$auth contains connected account data
102
+ // Authentication and base request methods
103
+ _getAuthHeaders ( ) {
104
+ const headers = {
105
+ "Content-Type" : "application/json" ,
106
+ "User-Agent" : "Pipedream/1.0" ,
107
+ } ;
108
+
109
+ if ( this . $auth ?. api_key ) {
110
+ headers [ "apikey" ] = this . $auth . api_key ;
111
+ }
112
+
113
+ if ( this . $auth ?. oauth_access_token ) {
114
+ headers [ "Authorization" ] = `Bearer ${ this . $auth . oauth_access_token } ` ;
115
+ }
116
+
117
+ return headers ;
118
+ } ,
119
+
120
+ async _makeRequest ( { endpoint, method = "GET" , params = { } , data = null , ...args } ) {
121
+ const url = `${ BASE_URL } ${ endpoint } ` ;
122
+ const headers = this . _getAuthHeaders ( ) ;
123
+
124
+ const config = {
125
+ method,
126
+ url,
127
+ headers,
128
+ params : formatQueryParams ( params ) ,
129
+ timeout : 30000 ,
130
+ ...args ,
131
+ } ;
132
+
133
+ if ( data ) {
134
+ config . data = data ;
135
+ }
136
+
137
+ try {
138
+ const response = await axios ( this , config ) ;
139
+ return response . data || response ;
140
+ } catch ( error ) {
141
+ const parsedError = parseApiError ( error ) ;
142
+ throw new Error ( `Trustpilot API Error: ${ parsedError . message } (${ parsedError . code } )` ) ;
143
+ }
144
+ } ,
145
+
146
+ async _makeRequestWithRetry ( config , retries = RETRY_CONFIG . MAX_RETRIES ) {
147
+ try {
148
+ return await this . _makeRequest ( config ) ;
149
+ } catch ( error ) {
150
+ if ( retries > 0 && error . response ?. status === HTTP_STATUS . TOO_MANY_REQUESTS ) {
151
+ const delay = Math . min ( RETRY_CONFIG . INITIAL_DELAY * ( RETRY_CONFIG . MAX_RETRIES - retries + 1 ) , RETRY_CONFIG . MAX_DELAY ) ;
152
+ await sleep ( delay ) ;
153
+ return this . _makeRequestWithRetry ( config , retries - 1 ) ;
154
+ }
155
+ throw error ;
156
+ }
157
+ } ,
158
+
159
+ // Business Unit methods
160
+ async getBusinessUnit ( businessUnitId ) {
161
+ if ( ! validateBusinessUnitId ( businessUnitId ) ) {
162
+ throw new Error ( "Invalid business unit ID" ) ;
163
+ }
164
+
165
+ const endpoint = buildUrl ( ENDPOINTS . BUSINESS_UNIT_BY_ID , { businessUnitId } ) ;
166
+ const response = await this . _makeRequest ( { endpoint } ) ;
167
+ return parseBusinessUnit ( response ) ;
168
+ } ,
169
+
170
+ async searchBusinessUnits ( { query = "" , limit = DEFAULT_LIMIT , offset = 0 } = { } ) {
171
+ const response = await this . _makeRequest ( {
172
+ endpoint : ENDPOINTS . BUSINESS_UNITS ,
173
+ params : {
174
+ query,
175
+ limit,
176
+ offset,
177
+ } ,
178
+ } ) ;
179
+
180
+ return response . businessUnits ?. map ( parseBusinessUnit ) || [ ] ;
181
+ } ,
182
+
183
+ // Service Review methods
184
+ async getServiceReviews ( {
185
+ businessUnitId,
186
+ stars = null ,
187
+ sortBy = SORT_OPTIONS . CREATED_AT_DESC ,
188
+ limit = DEFAULT_LIMIT ,
189
+ offset = 0 ,
190
+ includeReportedReviews = false ,
191
+ tags = [ ] ,
192
+ language = null ,
193
+ } = { } ) {
194
+ if ( ! validateBusinessUnitId ( businessUnitId ) ) {
195
+ throw new Error ( "Invalid business unit ID" ) ;
196
+ }
197
+
198
+ const endpoint = buildUrl ( ENDPOINTS . PRIVATE_SERVICE_REVIEWS , { businessUnitId } ) ;
199
+ const params = {
200
+ stars,
201
+ orderBy : sortBy ,
202
+ perPage : limit ,
203
+ page : Math . floor ( offset / limit ) + 1 ,
204
+ includeReportedReviews,
205
+ language,
206
+ } ;
207
+
208
+ if ( tags . length > 0 ) {
209
+ params . tags = tags . join ( "," ) ;
210
+ }
211
+
212
+ const response = await this . _makeRequestWithRetry ( {
213
+ endpoint,
214
+ params,
215
+ } ) ;
216
+
217
+ return {
218
+ reviews : response . reviews ?. map ( parseReview ) || [ ] ,
219
+ pagination : {
220
+ total : response . pagination ?. total || 0 ,
221
+ page : response . pagination ?. page || 1 ,
222
+ perPage : response . pagination ?. perPage || limit ,
223
+ hasMore : response . pagination ?. hasMore || false ,
224
+ } ,
225
+ } ;
226
+ } ,
227
+
228
+ async getServiceReviewById ( { businessUnitId, reviewId } ) {
229
+ if ( ! validateBusinessUnitId ( businessUnitId ) ) {
230
+ throw new Error ( "Invalid business unit ID" ) ;
231
+ }
232
+ if ( ! validateReviewId ( reviewId ) ) {
233
+ throw new Error ( "Invalid review ID" ) ;
234
+ }
235
+
236
+ const endpoint = buildUrl ( ENDPOINTS . PRIVATE_SERVICE_REVIEW_BY_ID , { businessUnitId, reviewId } ) ;
237
+ const response = await this . _makeRequest ( { endpoint } ) ;
238
+ return parseReview ( response ) ;
239
+ } ,
240
+
241
+ async replyToServiceReview ( { businessUnitId, reviewId, message } ) {
242
+ if ( ! validateBusinessUnitId ( businessUnitId ) ) {
243
+ throw new Error ( "Invalid business unit ID" ) ;
244
+ }
245
+ if ( ! validateReviewId ( reviewId ) ) {
246
+ throw new Error ( "Invalid review ID" ) ;
247
+ }
248
+ if ( ! message || typeof message !== 'string' ) {
249
+ throw new Error ( "Reply message is required" ) ;
250
+ }
251
+
252
+ const endpoint = buildUrl ( ENDPOINTS . REPLY_TO_SERVICE_REVIEW , { businessUnitId, reviewId } ) ;
253
+ const response = await this . _makeRequest ( {
254
+ endpoint,
255
+ method : "POST" ,
256
+ data : { message } ,
257
+ } ) ;
258
+ return response ;
259
+ } ,
260
+
261
+ // Product Review methods
262
+ async getProductReviews ( {
263
+ businessUnitId,
264
+ stars = null ,
265
+ sortBy = SORT_OPTIONS . CREATED_AT_DESC ,
266
+ limit = DEFAULT_LIMIT ,
267
+ offset = 0 ,
268
+ includeReportedReviews = false ,
269
+ tags = [ ] ,
270
+ language = null ,
271
+ } = { } ) {
272
+ if ( ! validateBusinessUnitId ( businessUnitId ) ) {
273
+ throw new Error ( "Invalid business unit ID" ) ;
274
+ }
275
+
276
+ const endpoint = buildUrl ( ENDPOINTS . PRIVATE_PRODUCT_REVIEWS , { businessUnitId } ) ;
277
+ const params = {
278
+ stars,
279
+ orderBy : sortBy ,
280
+ perPage : limit ,
281
+ page : Math . floor ( offset / limit ) + 1 ,
282
+ includeReportedReviews,
283
+ language,
284
+ } ;
285
+
286
+ if ( tags . length > 0 ) {
287
+ params . tags = tags . join ( "," ) ;
288
+ }
289
+
290
+ const response = await this . _makeRequestWithRetry ( {
291
+ endpoint,
292
+ params,
293
+ } ) ;
294
+
295
+ return {
296
+ reviews : response . reviews ?. map ( parseReview ) || [ ] ,
297
+ pagination : {
298
+ total : response . pagination ?. total || 0 ,
299
+ page : response . pagination ?. page || 1 ,
300
+ perPage : response . pagination ?. perPage || limit ,
301
+ hasMore : response . pagination ?. hasMore || false ,
302
+ } ,
303
+ } ;
304
+ } ,
305
+
306
+ async getProductReviewById ( { reviewId } ) {
307
+ if ( ! validateReviewId ( reviewId ) ) {
308
+ throw new Error ( "Invalid review ID" ) ;
309
+ }
310
+
311
+ const endpoint = buildUrl ( ENDPOINTS . PRIVATE_PRODUCT_REVIEW_BY_ID , { reviewId } ) ;
312
+ const response = await this . _makeRequest ( { endpoint } ) ;
313
+ return parseReview ( response ) ;
314
+ } ,
315
+
316
+ async replyToProductReview ( { reviewId, message } ) {
317
+ if ( ! validateReviewId ( reviewId ) ) {
318
+ throw new Error ( "Invalid review ID" ) ;
319
+ }
320
+ if ( ! message || typeof message !== 'string' ) {
321
+ throw new Error ( "Reply message is required" ) ;
322
+ }
323
+
324
+ const endpoint = buildUrl ( ENDPOINTS . REPLY_TO_PRODUCT_REVIEW , { reviewId } ) ;
325
+ const response = await this . _makeRequest ( {
326
+ endpoint,
327
+ method : "POST" ,
328
+ data : { message } ,
329
+ } ) ;
330
+ return response ;
331
+ } ,
332
+
333
+ // Webhook methods
334
+ async createWebhook ( { url, events = [ ] , businessUnitId = null } ) {
335
+ if ( ! url ) {
336
+ throw new Error ( "Webhook URL is required" ) ;
337
+ }
338
+ if ( ! Array . isArray ( events ) || events . length === 0 ) {
339
+ throw new Error ( "At least one event must be specified" ) ;
340
+ }
341
+
342
+ const data = {
343
+ url,
344
+ events,
345
+ } ;
346
+
347
+ if ( businessUnitId ) {
348
+ data . businessUnitId = businessUnitId ;
349
+ }
350
+
351
+ const response = await this . _makeRequest ( {
352
+ endpoint : ENDPOINTS . WEBHOOKS ,
353
+ method : "POST" ,
354
+ data,
355
+ } ) ;
356
+ return response ;
357
+ } ,
358
+
359
+ async deleteWebhook ( webhookId ) {
360
+ if ( ! webhookId ) {
361
+ throw new Error ( "Webhook ID is required" ) ;
362
+ }
363
+
364
+ const endpoint = buildUrl ( ENDPOINTS . WEBHOOK_BY_ID , { webhookId } ) ;
365
+ await this . _makeRequest ( {
366
+ endpoint,
367
+ method : "DELETE" ,
368
+ } ) ;
369
+ } ,
370
+
371
+ async listWebhooks ( ) {
372
+ const response = await this . _makeRequest ( {
373
+ endpoint : ENDPOINTS . WEBHOOKS ,
374
+ } ) ;
375
+ return response . webhooks || [ ] ;
376
+ } ,
377
+
378
+ // Utility methods
379
+ parseWebhookPayload ( payload ) {
380
+ return parseWebhookPayload ( payload ) ;
381
+ } ,
382
+
383
+ validateWebhookSignature ( payload , signature , secret ) {
384
+ // TODO: Implement webhook signature validation when Trustpilot provides it
385
+ return true ;
386
+ } ,
387
+
388
+ // Legacy method for debugging
9
389
authKeys ( ) {
10
- console . log ( Object . keys ( this . $auth ) ) ;
390
+ console . log ( "Auth keys:" , Object . keys ( this . $auth || { } ) ) ;
391
+ return Object . keys ( this . $auth || { } ) ;
11
392
} ,
12
393
} ,
13
394
} ) ;
0 commit comments