From c26cec2aae5a6c761d2c3d080e64acdfff5980ad Mon Sep 17 00:00:00 2001 From: csulham Date: Tue, 19 Mar 2024 13:50:04 -0400 Subject: [PATCH] Adding the beginnings of the push API handler, and some refactoring --- package-lock.json | 6 +-- package.json | 2 +- pages/api/onPublishEnd.ts | 79 +++++++++++++++++++++++++++++++++++++++ pages/api/yextCrawl.ts | 1 + pages/api/yextQuery.ts | 29 +------------- util/GetDate.tsx | 19 ++++++++++ util/GraphQLQuery.tsx | 39 +++++++++++++++++++ 7 files changed, 144 insertions(+), 31 deletions(-) create mode 100644 pages/api/onPublishEnd.ts create mode 100644 util/GetDate.tsx create mode 100644 util/GraphQLQuery.tsx diff --git a/package-lock.json b/package-lock.json index d782eab..e51e58b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -304,9 +304,9 @@ "dev": true }, "@types/node": { - "version": "20.9.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.0.tgz", - "integrity": "sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==", + "version": "20.11.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.30.tgz", + "integrity": "sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==", "dev": true, "requires": { "undici-types": "~5.26.4" diff --git a/package.json b/package.json index d5e8a5c..9d28348 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "react-dom": "^18" }, "devDependencies": { - "@types/node": "^20", + "@types/node": "^20.11.30", "@types/react": "^18", "@types/react-dom": "^18", "autoprefixer": "^10.0.1", diff --git a/pages/api/onPublishEnd.ts b/pages/api/onPublishEnd.ts new file mode 100644 index 0000000..6b2a46b --- /dev/null +++ b/pages/api/onPublishEnd.ts @@ -0,0 +1,79 @@ +// Import the Next.js API route handler +import { NextApiRequest, NextApiResponse } from 'next'; +import { graphqlRequest, GraphQLRequest } from '@/util/GraphQLQuery'; + +// Define the API route handler +export default async function onPublishEnd(req: NextApiRequest, res: NextApiResponse) { + // Check if the api_key query parameter matches the WEBHOOK_API_KEY environment variable + if (req.query.api_key !== process.env.WEBHOOK_API_KEY) { + return res.status(401).json({ message: 'Unauthorized' }); + } + + // If the request method is not POST, return an error + if (req.method !== 'POST') { + return res.status(405).json({ message: 'Method not allowed' }); + } + + let data; + try { + // Try to parse the JSON data from the request body + //console.log('Req body:\n' + JSON.stringify(req.body)); + data = req.body; + } catch (error) { + console.log('Bad Request: ', error); + return res.status(400).json({ message: 'Bad Request. Check incoming data.' }); + } + + // Loop over all the entries in updates + for (const update of data.updates) { + // Check if the entity_definition is LayoutData + if (update.entity_definition === 'LayoutData') { + // Extract the GUID portion of the identifier + const guid = update.identifier.split('-')[0] + + try { + // Create the GraphQL request + const request: GraphQLRequest = { + query: itemQuery, + variables: { id: guid }, + }; + console.log('Getting GQL Data for item ' + guid); + // Invoke the GraphQL query with the request + const result = await graphqlRequest(request); + + console.log('Item Data:\n' + JSON.stringify(result)); + + // TODO: Handle the result of the GraphQL query + // 1. Make sure we got some data from GQL + // 2. Check if it's in the right site (the webhook fires for every site) by comparing the path + // 3. Send the json data to the Yext Push API endpoint + + } catch (error) { + // If an error occurs while invoking the GraphQL query, return a 500 error + return res.status(500).json({ message: 'Internal Server Error: GraphQL query failed' }) + } + } + } + + // Send a response + return res.status(200).json({ message: 'Webhook event received' }) +} + +const itemQuery = ` +query ($id: String!) { + item(path: $id, language: "en") { + id + name + path + url { + path + url + } + fields { + name + jsonValue + } + } + } + +`; \ No newline at end of file diff --git a/pages/api/yextCrawl.ts b/pages/api/yextCrawl.ts index a256cb5..417e36d 100644 --- a/pages/api/yextCrawl.ts +++ b/pages/api/yextCrawl.ts @@ -1,4 +1,5 @@ import type { NextApiRequest, NextApiResponse } from 'next'; +import { graphqlRequest, GraphQLRequest } from '@/util/GraphQLQuery'; type ResponseData = { message: string; diff --git a/pages/api/yextQuery.ts b/pages/api/yextQuery.ts index bed7d15..df0a1b2 100644 --- a/pages/api/yextQuery.ts +++ b/pages/api/yextQuery.ts @@ -1,4 +1,5 @@ import type { NextApiRequest, NextApiResponse } from 'next'; +import { GetDate } from '@/util/GetDate'; type ResponseData = { message: string; @@ -20,7 +21,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse< sort: req.query.sort?.toString() ?? '', }; - console.log('Yext Query Incoming params: \n' + JSON.stringify(incomingParams)); + //console.log('Yext Query Incoming params: \n' + JSON.stringify(incomingParams)); if (isNaN(incomingParams.numResults) || incomingParams.numResults < 1) { // Ensure this is a number, otherwise set to 9. @@ -106,26 +107,6 @@ function GetTaxonomyFilterClause(name: string, values?: string): string { return JSON.stringify(filterClause); } -function GetDate(): string { - const today = new Date(); - const yyyy = today.getFullYear(); - const mm = today.getMonth() + 1; // Months start at 0! - const dd = today.getDate(); - let MM = mm.toString(); - let DD = dd.toString(); - - if (dd < 10) { - DD = '0' + dd; - } - - if (mm < 10) { - MM = '0' + mm; - } - - // Return date in YYYYMMDD format for Yext param - return yyyy + MM + DD; -} - /** * @description Data structure that should be serialized into URL query parameters for a Yext entities API search. * Page parameter starts at 1. @@ -135,14 +116,8 @@ function GetDate(): string { * Incorrect: params.Topics = '{AC5AB52E-0319-4166-9847-B31CC071CA7E},{C4FA9F40-8CE1-4725-B180-13EC9E09D21F}' */ export type YextSearchParams = { - //audience?: string; - //authors?: string; - //blogCategories?: string; contentTypes?: string; - //eventTypes?: string; locations?: string; - //memberStatus?: string; - //products?: string; topics?: string; page: number; numResults: number; diff --git a/util/GetDate.tsx b/util/GetDate.tsx new file mode 100644 index 0000000..3a03e9f --- /dev/null +++ b/util/GetDate.tsx @@ -0,0 +1,19 @@ +export function GetDate(): string { + const today = new Date(); + const yyyy = today.getFullYear(); + const mm = today.getMonth() + 1; // Months start at 0! + const dd = today.getDate(); + let MM = mm.toString(); + let DD = dd.toString(); + + if (dd < 10) { + DD = '0' + dd; + } + + if (mm < 10) { + MM = '0' + mm; + } + + // Return date in YYYYMMDD format for Yext param + return yyyy + MM + DD; +} \ No newline at end of file diff --git a/util/GraphQLQuery.tsx b/util/GraphQLQuery.tsx new file mode 100644 index 0000000..8e04680 --- /dev/null +++ b/util/GraphQLQuery.tsx @@ -0,0 +1,39 @@ +import fetch from 'node-fetch'; + +export interface GraphQLRequest { + query: string; + variables?: Record; +} + +// Define the expected shape of the GraphQL response data +interface GraphQLResponse { + data?: any; + errors?: { message: string }[]; +} + +export async function graphqlRequest(request: GraphQLRequest): Promise { + const endpoint = process.env.GRAPH_QL_ENDPOINT ?? ''; + const apiKey = process.env.SITECORE_API_KEY ?? ''; + + const response = await fetch(endpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'sc_apikey': apiKey + }, + body: JSON.stringify(request), + }); + + if (!response.ok) { + throw new Error(`GraphQL request failed: ${response.status} ${response.statusText}`); + } + + // Type the body variable using the GraphQLResponse interface + const body = await response.json() as GraphQLResponse; + + if (body.errors) { + throw new Error(`GraphQL request failed: ${body.errors.map(error => error.message).join(', ')}`); + } + + return body.data; +}