From b504bd578a601a707a71bf8083b0a4f0e10a5567 Mon Sep 17 00:00:00 2001 From: Tanner Heffner Date: Wed, 1 Nov 2023 09:10:13 -0700 Subject: [PATCH 1/6] update preview routes, use auth for drupal client --- envs/.env.example | 11 +++++++---- src/lib/drupal/drupalClient.ts | 6 ++++++ src/pages/api/exit-preview.tsx | 2 +- src/pages/api/preview.tsx | 10 ++++++++-- 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/envs/.env.example b/envs/.env.example index 5d2cf02bc..ae2ba6b96 100644 --- a/envs/.env.example +++ b/envs/.env.example @@ -2,11 +2,14 @@ NEXT_PUBLIC_DRUPAL_BASE_URL=https://content-build-medc0xjkxm4jmpzxl3tfbcs7qcddsivh.ci.cms.va.gov NEXT_IMAGE_DOMAIN=https://content-build-medc0xjkxm4jmpzxl3tfbcs7qcddsivh.ci.cms.va.gov +# If running va.gov-cms locally +# NEXT_PUBLIC_DRUPAL_BASE_URL=https://va-gov-cms.ddev.site +# NEXT_IMAGE_DOMAIN=https://va-gov-cms.ddev.site + # for Drupal preview -DRUPAL_SITE_ID= -DRUPAL_PREVIEW_SECRET= -DRUPAL_CLIENT_ID= -DRUPAL_CLIENT_SECRET= +DRUPAL_PREVIEW_SECRET=secret +DRUPAL_CLIENT_ID=Retrieve this from AWS SSM /cms/consumers/next-build/client_id +DRUPAL_CLIENT_SECRET=Retrieve this from AWS SSM /cms/consumers/next-build/client_secret # for local sitemap generation SITE_URL=http://localhost:8001 diff --git a/src/lib/drupal/drupalClient.ts b/src/lib/drupal/drupalClient.ts index d761938ae..34e7df7c7 100644 --- a/src/lib/drupal/drupalClient.ts +++ b/src/lib/drupal/drupalClient.ts @@ -62,4 +62,10 @@ export const drupalClient = new DrupalClient(baseUrl, { fetcher, useDefaultResourceTypeEntry: true, throwJsonApiErrors: false, + auth: { + clientId: process.env.DRUPAL_CLIENT_ID, + clientSecret: process.env.DRUPAL_CLIENT_SECRET, + }, + previewSecret: process.env.DRUPAL_PREVIEW_SECRET, + forceIframeSameSiteCookie: process.env.NODE_ENV === 'development', }) diff --git a/src/pages/api/exit-preview.tsx b/src/pages/api/exit-preview.tsx index 6d29b0c1c..b7e302fdb 100644 --- a/src/pages/api/exit-preview.tsx +++ b/src/pages/api/exit-preview.tsx @@ -1,6 +1,6 @@ import { NextApiResponse } from 'next' -export default async function exit(_, response: NextApiResponse) { +export default function exit(_, response: NextApiResponse) { response.clearPreviewData() response.writeHead(307, { Location: '/' }) response.end() diff --git a/src/pages/api/preview.tsx b/src/pages/api/preview.tsx index 39fa2fca4..9c04e494e 100644 --- a/src/pages/api/preview.tsx +++ b/src/pages/api/preview.tsx @@ -1,3 +1,9 @@ -import { DrupalPreview } from 'next-drupal' +import { NextApiRequest, NextApiResponse } from 'next' +import { drupalClient } from '@/lib/drupal/drupalClient' -export default DrupalPreview() +export default async function handler( + request: NextApiRequest, + response: NextApiResponse +) { + await drupalClient.preview(request, response) +} From e732551f93f8daf36fe451a8b527372ca3f6a4f6 Mon Sep 17 00:00:00 2001 From: Tanner Heffner Date: Wed, 1 Nov 2023 09:54:13 -0700 Subject: [PATCH 2/6] note in .env.tugboat --- envs/.env.tugboat | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/envs/.env.tugboat b/envs/.env.tugboat index bc8b2c13e..1a0bd8248 100644 --- a/envs/.env.tugboat +++ b/envs/.env.tugboat @@ -12,3 +12,7 @@ NEXT_PUBLIC_ASSETS_URL=/generated/ GOOGLE_TAG_MANAGER_AUTH= GOOGLE_TAG_MANAGER_PREVIEW= GOOGLE_TAG_MANAGER_ID= + +# For Drupal preview +DRUPAL_PREVIEW_SECRET=secret +# DRUPAL_CLIENT_ID & DRUPAL_CLIENT_SECRET are made available through Tugboat UI settings. From 634c225967d8abf581a2258d46ef910ec0a12b8f Mon Sep 17 00:00:00 2001 From: Tanner Heffner Date: Wed, 1 Nov 2023 10:05:27 -0700 Subject: [PATCH 3/6] update test env vars --- envs/.env.test | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/envs/.env.test b/envs/.env.test index e47174e2a..e6f79c85e 100644 --- a/envs/.env.test +++ b/envs/.env.test @@ -1,10 +1,9 @@ # This is the standard lower environment for Content API. NEXT_PUBLIC_DRUPAL_BASE_URL=https://content-build-medc0xjkxm4jmpzxl3tfbcs7qcddsivh.ci.cms.va.gov NEXT_IMAGE_DOMAIN=https://content-build-medc0xjkxm4jmpzxl3tfbcs7qcddsivh.ci.cms.va.gov -DRUPAL_SITE_ID= -DRUPAL_PREVIEW_SECRET= -DRUPAL_CLIENT_ID= -DRUPAL_CLIENT_SECRET= +DRUPAL_PREVIEW_SECRET=secret +DRUPAL_CLIENT_ID=foo +DRUPAL_CLIENT_SECRET=bar GOOGLE_TAG_MANAGER_AUTH= GOOGLE_TAG_MANAGER_PREVIEW= GOOGLE_TAG_MANAGER_ID= From a9b967ea954c99298c52f8acba4265468ff4bd80 Mon Sep 17 00:00:00 2001 From: Tanner Heffner Date: Wed, 1 Nov 2023 12:46:23 -0700 Subject: [PATCH 4/6] start to stub in preview checks --- src/data/queries/newsStory.ts | 26 +++++++++++++++++++------- src/lib/drupal/staticProps.ts | 1 + src/pages/[[...slug]].tsx | 12 ++++++++---- 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/data/queries/newsStory.ts b/src/data/queries/newsStory.ts index c6f6165ad..c82b06432 100644 --- a/src/data/queries/newsStory.ts +++ b/src/data/queries/newsStory.ts @@ -8,6 +8,7 @@ import { drupalClient } from '@/lib/drupal/drupalClient' import { queries } from '.' import { NodeNewsStory } from '@/types/dataTypes/drupal/node' import { NewsStoryType } from '@/types/index' +import { GetServerSidePropsContext } from 'next' // Define the query params for fetching node--news_story. export const params: QueryParams = () => { @@ -25,19 +26,30 @@ export const params: QueryParams = () => { // Define the option types for the data loader. export type NewsStoryDataOpts = QueryOpts<{ id: string + context?: GetServerSidePropsContext }> // Implement the data loader. export const data: QueryData = async ( opts ): Promise => { - const entity = await drupalClient.getResource( - 'node--news_story', - opts?.id, - { - params: params().getQueryObject(), - } - ) + const entity = opts.context.preview + ? // need to use getResourceFromContext for unpublished revisions + await drupalClient.getResourceFromContext( + 'node--news_story', + opts.context, + { + params: params().getQueryObject(), + } + ) + : // otherwise just lookup by uuid + await drupalClient.getResource( + 'node--news_story', + opts.id, + { + params: params().getQueryObject(), + } + ) return entity } diff --git a/src/lib/drupal/staticProps.ts b/src/lib/drupal/staticProps.ts index 4aedcac2b..9e645dfb6 100644 --- a/src/lib/drupal/staticProps.ts +++ b/src/lib/drupal/staticProps.ts @@ -86,6 +86,7 @@ export function getStaticPropsQueryOpts( if (resourceType === RESOURCE_TYPES.STORY) { return { id, + context: context.preview ? context : null, } } diff --git a/src/pages/[[...slug]].tsx b/src/pages/[[...slug]].tsx index b530898d0..6cecda42b 100644 --- a/src/pages/[[...slug]].tsx +++ b/src/pages/[[...slug]].tsx @@ -105,9 +105,12 @@ export async function getStaticProps(context: GetStaticPropsContext) { const expandedContext = getExpandedStaticPropsContext(context) // Now that we have a path, translate for resource endpoint - const pathInfo = await drupalClient.translatePath( - expandedContext.drupalPath - ) + // need to use translatePathFromContext here for previewing unpublished revisions + const pathInfo = + expandedContext.listing.isListingPage === false + ? await drupalClient.translatePathFromContext(expandedContext) + : await drupalClient.translatePath(expandedContext.drupalPath) + if (!pathInfo) { return { notFound: true, @@ -130,7 +133,7 @@ export async function getStaticProps(context: GetStaticPropsContext) { // If we're not in preview mode and the resource is not published, // Return page not found. - if (!context.preview && resource?.published === false) { + if (!context.preview && !resource?.published) { return { notFound: true, } @@ -145,6 +148,7 @@ export async function getStaticProps(context: GetStaticPropsContext) { return { props: { + preview: context.preview || false, resource, bannerData, headerFooterData, From b2dfac2abf2fdff6354f13984dd852c743db8699 Mon Sep 17 00:00:00 2001 From: Tanner Heffner Date: Thu, 2 Nov 2023 15:42:19 -0700 Subject: [PATCH 5/6] preview works for stories and story listing --- src/data/queries/storyListing.ts | 24 +++++++++++++++------- src/lib/drupal/listingPages.ts | 3 ++- src/lib/drupal/staticProps.ts | 16 +++++++-------- src/pages/[[...slug]].tsx | 34 +++++++++++++++++++++++++------- 4 files changed, 53 insertions(+), 24 deletions(-) diff --git a/src/data/queries/storyListing.ts b/src/data/queries/storyListing.ts index 7a9f34237..638e7d28f 100644 --- a/src/data/queries/storyListing.ts +++ b/src/data/queries/storyListing.ts @@ -41,13 +41,23 @@ type StoryListingData = { export const data: QueryData = async ( opts ) => { - const entity = await drupalClient.getResource( - 'node--story_listing', - opts?.id, - { - params: params().getQueryObject(), - } - ) + const entity = opts.context.preview + ? // need to use getResourceFromContext for unpublished revisions + await drupalClient.getResourceFromContext( + 'node--story_listing', + opts.context, + { + params: params().getQueryObject(), + } + ) + : // otherwise just lookup by uuid + await drupalClient.getResource( + 'node--story_listing', + opts.id, + { + params: params().getQueryObject(), + } + ) // Fetch list of stories related to this listing const { diff --git a/src/lib/drupal/listingPages.ts b/src/lib/drupal/listingPages.ts index a48573bad..38ceb123c 100644 --- a/src/lib/drupal/listingPages.ts +++ b/src/lib/drupal/listingPages.ts @@ -2,7 +2,7 @@ import { drupalClient } from '@/lib/drupal/drupalClient' import { QUERIES_MAP, queries } from '@/data/queries' import { RESOURCE_TYPES, ResourceTypeType } from '@/lib/constants/resourceTypes' import { StaticPathResourceType } from '@/types/index' -import { GetStaticPropsContext } from 'next' +import { GetServerSidePropsContext, GetStaticPropsContext } from 'next' import { QueryOpts } from 'next-drupal-query' import { LovellStaticPropsContextProps } from '@/lib/drupal/lovell/types' import { isLovellChildVariantResource } from '@/lib/drupal/lovell/utils' @@ -30,6 +30,7 @@ export type ListingPageDataOpts = QueryOpts<{ id: string page?: number lovell?: LovellStaticPropsContextProps + context?: GetServerSidePropsContext }> type ListingPageCounts = { diff --git a/src/lib/drupal/staticProps.ts b/src/lib/drupal/staticProps.ts index 9e645dfb6..baee68b55 100644 --- a/src/lib/drupal/staticProps.ts +++ b/src/lib/drupal/staticProps.ts @@ -83,23 +83,21 @@ export function getStaticPropsQueryOpts( id: string, context: ExpandedStaticPropsContext ): StaticPropsQueryOpts { - if (resourceType === RESOURCE_TYPES.STORY) { - return { - id, - context: context.preview ? context : null, - } + // most types will simply need an ID, preview mode needs context + const defaultQueryOpts = { + id, + context: context.preview ? context : null, } + // Listing Page types need to know what page # to query for if (resourceType === RESOURCE_TYPES.STORY_LISTING) { return { - id, + ...defaultQueryOpts, page: context.listing.page, } } - return { - id, - } + return defaultQueryOpts } export async function fetchSingleStaticPropsResource( diff --git a/src/pages/[[...slug]].tsx b/src/pages/[[...slug]].tsx index 6cecda42b..5a7d85bbf 100644 --- a/src/pages/[[...slug]].tsx +++ b/src/pages/[[...slug]].tsx @@ -32,6 +32,7 @@ export default function ResourcePage({ resource, bannerData, headerFooterData, + preview, }) { if (!resource) return null @@ -52,6 +53,23 @@ export default function ResourcePage({ {/* todo: do all meta tags correctly, currently this fixes an error on news story */} + + {preview && ( + + )} + Date: Thu, 2 Nov 2023 16:26:40 -0700 Subject: [PATCH 6/6] use ? so static paths can build without context present --- src/data/queries/newsStory.ts | 2 +- src/data/queries/storyListing.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/data/queries/newsStory.ts b/src/data/queries/newsStory.ts index c82b06432..ba32b72c2 100644 --- a/src/data/queries/newsStory.ts +++ b/src/data/queries/newsStory.ts @@ -33,7 +33,7 @@ export type NewsStoryDataOpts = QueryOpts<{ export const data: QueryData = async ( opts ): Promise => { - const entity = opts.context.preview + const entity = opts?.context?.preview ? // need to use getResourceFromContext for unpublished revisions await drupalClient.getResourceFromContext( 'node--news_story', diff --git a/src/data/queries/storyListing.ts b/src/data/queries/storyListing.ts index 638e7d28f..4802cdf5b 100644 --- a/src/data/queries/storyListing.ts +++ b/src/data/queries/storyListing.ts @@ -41,7 +41,7 @@ type StoryListingData = { export const data: QueryData = async ( opts ) => { - const entity = opts.context.preview + const entity = opts?.context?.preview ? // need to use getResourceFromContext for unpublished revisions await drupalClient.getResourceFromContext( 'node--story_listing',