Skip to content

Commit ed011b4

Browse files
ktzmishraKshitiz Mishra
andauthored
feat: add edge-fetch API endpoint for Tokowaka URL fetching (#1600)
Co-authored-by: Kshitiz Mishra <kshitizm@adobe.com>
1 parent 48a85f8 commit ed011b4

File tree

6 files changed

+526
-0
lines changed

6 files changed

+526
-0
lines changed

docs/openapi/api.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,8 @@ paths:
220220
$ref: './site-opportunities.yaml#/site-opportunity-suggestions-status'
221221
/sites/{siteId}/opportunities/{opportunityId}/suggestions/auto-fix:
222222
$ref: './site-opportunities.yaml#/site-opportunity-suggestions-auto-fix'
223+
/sites/{siteId}/opportunities/{opportunityId}/suggestions/edge-live-preview:
224+
$ref: './site-opportunities.yaml#/site-opportunity-suggestions-edge-live-preview'
223225
/sites/{siteId}/site-enrollments:
224226
$ref: './site-enrollments-api.yaml#/site-enrollments-by-site'
225227
/sites/{siteId}/user-activities:

docs/openapi/site-opportunities.yaml

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,83 @@ site-opportunity-suggestion:
610610
security:
611611
- ims_key: [ ]
612612

613+
site-opportunity-suggestions-edge-live-preview:
614+
parameters:
615+
- $ref: './parameters.yaml#/siteId'
616+
- $ref: './parameters.yaml#/opportunityId'
617+
post:
618+
operationId: edgeLivePreview
619+
summary: |
620+
Fetch live content from a URL using Tokowaka-AI User-Agent
621+
description: |
622+
Fetches live content from a given URL using the Tokowaka-AI User-Agent.
623+
This is useful for previewing what content is currently deployed or accessible
624+
from a specific URL.
625+
tags:
626+
- opportunity-suggestions
627+
requestBody:
628+
required: true
629+
content:
630+
application/json:
631+
schema:
632+
type: object
633+
required:
634+
- url
635+
properties:
636+
url:
637+
type: string
638+
format: uri
639+
description: The URL to fetch content from
640+
example: https://www.lovesac.com/sactionals
641+
responses:
642+
'200':
643+
description: Successfully fetched content from the URL
644+
content:
645+
application/json:
646+
schema:
647+
type: object
648+
properties:
649+
status:
650+
type: string
651+
enum: [success, error]
652+
description: Fetch status
653+
statusCode:
654+
type: integer
655+
description: HTTP status code from the URL
656+
message:
657+
type: string
658+
description: Error message if fetch failed
659+
html:
660+
type: object
661+
description: HTML content object (similar to edge-preview response)
662+
properties:
663+
url:
664+
type: string
665+
description: The requested URL
666+
content:
667+
type: string
668+
description: The fetched HTML content (null if fetch failed)
669+
example:
670+
status: success
671+
statusCode: 200
672+
html:
673+
url: https://www.lovesac.com/sactionals
674+
content: "<html>...</html>"
675+
'400':
676+
$ref: './responses.yaml#/400'
677+
'401':
678+
$ref: './responses.yaml#/401'
679+
'404':
680+
$ref: './responses.yaml#/404'
681+
'429':
682+
$ref: './responses.yaml#/429'
683+
'500':
684+
$ref: './responses.yaml#/500'
685+
'503':
686+
$ref: './responses.yaml#/503'
687+
security:
688+
- ims_key: [ ]
689+
613690
site-opportunity-suggestion-fixes:
614691
parameters:
615692
- $ref: './parameters.yaml#/siteId'

src/controllers/suggestions.js

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1298,12 +1298,122 @@ function SuggestionsController(ctx, sqs, env) {
12981298
return createResponse(response, 207);
12991299
};
13001300

1301+
/**
1302+
* Fetches content from a URL using Tokowaka-AI User-Agent.
1303+
* This is a simple URL-based fetch, useful for checking deployed content.
1304+
* @param {Object} context of the request
1305+
* @returns {Promise<Response>} Fetch response with content
1306+
*/
1307+
const fetchFromEdge = async (context) => {
1308+
const siteId = context.params?.siteId;
1309+
const opportunityId = context.params?.opportunityId;
1310+
1311+
if (!isValidUUID(siteId)) {
1312+
return badRequest('Site ID required');
1313+
}
1314+
1315+
if (!isValidUUID(opportunityId)) {
1316+
return badRequest('Opportunity ID required');
1317+
}
1318+
1319+
// validate request body
1320+
if (!isNonEmptyObject(context.data)) {
1321+
return badRequest('No data provided');
1322+
}
1323+
1324+
const { url } = context.data;
1325+
1326+
// Validate URL
1327+
if (!hasText(url)) {
1328+
return badRequest('URL is required');
1329+
}
1330+
1331+
// Validate URL format
1332+
try {
1333+
const parsedUrl = new URL(url); // throws if invalid
1334+
if (!parsedUrl.protocol.startsWith('http')) {
1335+
return badRequest('Invalid URL format: only HTTP/HTTPS URLs are allowed');
1336+
}
1337+
} catch (error) {
1338+
return badRequest('Invalid URL format');
1339+
}
1340+
1341+
const site = await Site.findById(siteId);
1342+
if (!site) {
1343+
return notFound('Site not found');
1344+
}
1345+
1346+
if (!await accessControlUtil.hasAccess(site)) {
1347+
return forbidden('User does not belong to the organization');
1348+
}
1349+
1350+
const opportunity = await Opportunity.findById(opportunityId);
1351+
if (!opportunity || opportunity.getSiteId() !== siteId) {
1352+
return notFound('Opportunity not found');
1353+
}
1354+
1355+
try {
1356+
context.log.info(`Fetching content from URL: ${url}`);
1357+
1358+
// Make fetch request with Tokowaka-AI User-Agent
1359+
const response = await fetch(url, {
1360+
method: 'GET',
1361+
headers: {
1362+
'User-Agent': 'Tokowaka-AI Tokowaka/1.0',
1363+
Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
1364+
},
1365+
});
1366+
1367+
if (!response.ok) {
1368+
const requestId = response.headers.get('x-tokowaka-request-id');
1369+
const logMessage = requestId
1370+
? `Failed to fetch URL. Status: ${response.status}, x-tokowaka-request-id: ${requestId}`
1371+
: `Failed to fetch URL. Status: ${response.status}`;
1372+
context.log.warn(logMessage);
1373+
return ok({
1374+
status: 'error',
1375+
statusCode: response.status,
1376+
message: `Failed to fetch content from URL: ${url}`,
1377+
html: {
1378+
url,
1379+
content: null,
1380+
},
1381+
});
1382+
}
1383+
1384+
const content = await response.text();
1385+
1386+
context.log.info(`Successfully fetched content from URL: ${url}`);
1387+
1388+
return ok({
1389+
status: 'success',
1390+
statusCode: response.status,
1391+
html: {
1392+
url,
1393+
content,
1394+
},
1395+
});
1396+
} catch (error) {
1397+
context.log.error(`Error fetching from URL ${url}: ${error.message}`, error);
1398+
return ok({
1399+
status: 'error',
1400+
statusCode: 500,
1401+
message: `Error fetching content: ${error.message}`,
1402+
html: {
1403+
url,
1404+
content: null,
1405+
},
1406+
});
1407+
}
1408+
};
1409+
13011410
return {
13021411
autofixSuggestions,
13031412
createSuggestions,
13041413
deploySuggestionToEdge,
13051414
rollbackSuggestionFromEdge,
13061415
previewSuggestions,
1416+
fetchFromEdge,
13071417
getAllForOpportunity,
13081418
getAllForOpportunityPaged,
13091419
getByID,

src/routes/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ export default function getRouteHandlers(
197197
'POST /sites/:siteId/opportunities/:opportunityId/suggestions/edge-deploy': suggestionsController.deploySuggestionToEdge,
198198
'POST /sites/:siteId/opportunities/:opportunityId/suggestions/edge-rollback': suggestionsController.rollbackSuggestionFromEdge,
199199
'POST /sites/:siteId/opportunities/:opportunityId/suggestions/edge-preview': suggestionsController.previewSuggestions,
200+
'POST /sites/:siteId/opportunities/:opportunityId/suggestions/edge-live-preview': suggestionsController.fetchFromEdge,
200201
'GET /sites/:siteId/opportunities/:opportunityId/suggestions/by-status/:status': suggestionsController.getByStatus,
201202
'GET /sites/:siteId/opportunities/:opportunityId/suggestions/by-status/:status/paged/:limit/:cursor': suggestionsController.getByStatusPaged,
202203
'GET /sites/:siteId/opportunities/:opportunityId/suggestions/by-status/:status/paged/:limit': suggestionsController.getByStatusPaged,

0 commit comments

Comments
 (0)