From cf3d6477213073919d5241aba8507d52306ed4a3 Mon Sep 17 00:00:00 2001 From: Jeffrey Dowdle Date: Mon, 25 Sep 2023 08:23:37 +1000 Subject: [PATCH 1/3] feat(nuxt-ripple): pass through section-cache-tags response header --- .../nuxt-ripple/composables/use-tide-page.ts | 12 ++++++++++ packages/nuxt-ripple/server/api/tide/page.ts | 24 +++++++++++++++++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/packages/nuxt-ripple/composables/use-tide-page.ts b/packages/nuxt-ripple/composables/use-tide-page.ts index 9234375cd6..25a679149d 100644 --- a/packages/nuxt-ripple/composables/use-tide-page.ts +++ b/packages/nuxt-ripple/composables/use-tide-page.ts @@ -1,4 +1,5 @@ import type { TidePageBase } from './../types' +import { appendResponseHeader } from 'h3' import { useCookie, isPreviewPath, AuthCookieNames } from '#imports' const isCacheTimeExpired = (date: number, expiryInMinutes = 5) => { @@ -14,6 +15,7 @@ export const useTidePage = async ( ): Promise => { const route = useRoute() const path = slug || route.path + const event = useRequestEvent() const { public: config } = useRuntimeConfig() const siteId = site || config.tide?.site @@ -49,6 +51,8 @@ export const useTidePage = async ( headers.cookie = `${AuthCookieNames.ACCESS_TOKEN}=${accessTokenCookie.value};` } + let sectionCacheTags + if (!pageData.value) { const { data, error } = await useFetch('/api/tide/page', { key: `page-${path}`, @@ -59,11 +63,19 @@ export const useTidePage = async ( }, headers, async onResponse({ response }) { + sectionCacheTags = response.headers.get('section-cache-tags') + if (response.ok && response._data) { response._data['_fetched'] = Date.now() } } }) + + // Section.io cache tags must be set on the response header to invalidate the cache after a change in drupal + if (sectionCacheTags) { + appendResponseHeader(event, 'section-cache-tags', sectionCacheTags) + } + if (error && error.value?.statusCode) { useTideError(error.value?.statusCode) } diff --git a/packages/nuxt-ripple/server/api/tide/page.ts b/packages/nuxt-ripple/server/api/tide/page.ts index 64237f9627..938a74ed27 100644 --- a/packages/nuxt-ripple/server/api/tide/page.ts +++ b/packages/nuxt-ripple/server/api/tide/page.ts @@ -1,5 +1,11 @@ //@ts-nocheck runtime imports -import { defineEventHandler, getQuery, H3Event, getCookie } from 'h3' +import { + defineEventHandler, + getQuery, + H3Event, + getCookie, + setResponseHeader +} from 'h3' import { createHandler, TidePageApi } from '@dpc-sdp/ripple-tide-api' import { BadRequestError } from '@dpc-sdp/ripple-tide-api/errors' import { useNitroApp } from '#imports' @@ -31,7 +37,21 @@ export const createPageHandler = async ( headers['X-OAuth2-Authorization'] = `Bearer ${tokenCookie}` } - return await tidePageApi.getPageByPath(query.path, query.site, {}, headers) + const pageResponse = await tidePageApi.getPageByPath( + query.path, + query.site, + {}, + headers + ) + + // Need to pass on the section cache tags to the nuxt app + setResponseHeader( + event, + 'section-cache-tags', + pageResponse.headers['section-cache-tags'] + ) + + return pageResponse.data }) } From da09ec47c96bb5ef21b0c2e8889d3901ae285e40 Mon Sep 17 00:00:00 2001 From: Jeffrey Dowdle Date: Mon, 25 Sep 2023 11:40:16 +1000 Subject: [PATCH 2/3] refactor(@dpc-sdp/ripple-tide-api): refactored http client to not swallow up headers --- .../src/services/http-client.ts | 4 +- .../src/services/tide-api-base.ts | 16 +++++--- .../ripple-tide-api/src/services/tide-page.ts | 41 +++++++++++++------ .../ripple-tide-api/src/services/tide-site.ts | 4 +- .../server/api/tide/publication-index.ts | 9 ++-- 5 files changed, 49 insertions(+), 25 deletions(-) diff --git a/packages/ripple-tide-api/src/services/http-client.ts b/packages/ripple-tide-api/src/services/http-client.ts index 837ef622c2..df4b6c8ace 100644 --- a/packages/ripple-tide-api/src/services/http-client.ts +++ b/packages/ripple-tide-api/src/services/http-client.ts @@ -50,8 +50,8 @@ export default class HttpClient { _initializeResponseInterceptor() { this.client.interceptors.response.use( - ({ data }) => { - return data + (response) => { + return response }, (error) => { if (axios.isAxiosError(error)) { diff --git a/packages/ripple-tide-api/src/services/tide-api-base.ts b/packages/ripple-tide-api/src/services/tide-api-base.ts index 9eaea738a4..b27e0ae5b2 100644 --- a/packages/ripple-tide-api/src/services/tide-api-base.ts +++ b/packages/ripple-tide-api/src/services/tide-api-base.ts @@ -55,7 +55,7 @@ export default class TideApiBase extends HttpClient { return await this.getMappedDataAux(mapping, resource) } - async get(url: string, config = {}): Promise { + async get(url: string, config = {}): Promise<{ data: any; headers: any }> { try { return await this.client.get(url, { ...config }) } catch (error) { @@ -93,9 +93,12 @@ export default class TideApiBase extends HttpClient { } try { - const menusResponse = await this.get(`/menu_items/${menuName}`, { - params - }) + const { data: menusResponse } = await this.get( + `/menu_items/${menuName}`, + { + params + } + ) if (menusResponse?.data) { return getHierarchicalMenu(menusResponse.data, activePath) @@ -128,7 +131,7 @@ export default class TideApiBase extends HttpClient { async getAllPaginatedMenuLinks(siteId, menuName) { // Get the first page of links, this will also give us a link to the next page - let response = await this.get( + let { data: response } = await this.get( '/menu_link_content/menu_link_content?site=' + siteId, { params: { @@ -152,7 +155,8 @@ export default class TideApiBase extends HttpClient { // Get the rest of the menu links by following their 'next' link until a response has no next link while (response?.links?.next) { - response = await this.get(response.links.next.href) + const { data: nextResponse } = await this.get(response.links.next.href) + response = nextResponse menuLinks = [...menuLinks, ...response.data] } diff --git a/packages/ripple-tide-api/src/services/tide-page.ts b/packages/ripple-tide-api/src/services/tide-page.ts index 0d36d86424..8efad41d3d 100644 --- a/packages/ripple-tide-api/src/services/tide-page.ts +++ b/packages/ripple-tide-api/src/services/tide-page.ts @@ -88,8 +88,9 @@ export default class TidePageApi extends TideApiBase { this.path = path const routeUrl = `/route?site=${site}&path=${path}` + return this.get(routeUrl) - .then((response) => response?.data?.attributes) + .then((response) => response?.data?.data?.attributes) .catch((error) => { throw new NotFoundError( `Route for site "${site}" and path "${path}" not found`, @@ -137,7 +138,7 @@ export default class TidePageApi extends TideApiBase { } async getPageByShareLink(path: string, site: string) { - const response = await this.get(path).then((res) => { + const response = await this.get(path).then(({ data: res }) => { return res?.data ? jsonapiParse.parse(res).data || res.data : null }) @@ -249,12 +250,20 @@ export default class TidePageApi extends TideApiBase { this.sectionId = route.section const nodeUrl = `/${route.entity_type}/${route.bundle}/${route.uuid}` - return await this.get(nodeUrl, config).then((response) => { - if (response.data) { - const data = jsonapiParse.parse(response).data || response.data - return this.getTidePage(data, route) + + return await this.get(nodeUrl, config).then(({ data, headers }) => { + if (data.data) { + const parsedData = jsonapiParse.parse(data).data || data.data + return { + data: this.getTidePage(parsedData, route), + headers + } + } + + return { + data, + headers } - return response }) } throw new Error('Invalid route') @@ -286,7 +295,7 @@ export default class TidePageApi extends TideApiBase { } try { - const response = await this.get(`/node/${type}`, { + const { data: response } = await this.get(`/node/${type}`, { params }) if (response) { @@ -329,7 +338,9 @@ export default class TidePageApi extends TideApiBase { } try { // Give more time for list response, normally it's slow - const response = await this.get(`${entityType}/${bundle}`, { params }) + const { data: response } = await this.get(`${entityType}/${bundle}`, { + params + }) if (allPages) { const allPagesData = await this.getAllPaginatedData(response) @@ -365,10 +376,11 @@ export default class TidePageApi extends TideApiBase { // Use getByURL directly here because resource url contains all query params. try { - response = await this.get(resource, { + const { data: nextResponse } = await this.get(resource, { headers: headersConfig, site: this.site }) + response = nextResponse const nextData = parse ? jsonapiParse.parse(response).data : response.data @@ -418,9 +430,12 @@ export default class TidePageApi extends TideApiBase { site: this.site } try { - const response = await this.get(`/taxonomy_term/${taxonomyName}`, { - params - }) + const { data: response } = await this.get( + `/taxonomy_term/${taxonomyName}`, + { + params + } + ) if (response) { const resource = jsonapiParse.parse(response).data return resource diff --git a/packages/ripple-tide-api/src/services/tide-site.ts b/packages/ripple-tide-api/src/services/tide-site.ts index d49b399843..840bea507a 100644 --- a/packages/ripple-tide-api/src/services/tide-site.ts +++ b/packages/ripple-tide-api/src/services/tide-site.ts @@ -38,7 +38,9 @@ export default class TideSite extends TideApiBase { } } try { - const response = await this.get(`/taxonomy_term/sites`, { params }) + const { data: response } = await this.get(`/taxonomy_term/sites`, { + params + }) if (response && response.data.length > 0) { const resource = jsonapiParse.parse(response).data[0] const siteData = await this.getMappedData( diff --git a/packages/ripple-tide-publication/server/api/tide/publication-index.ts b/packages/ripple-tide-publication/server/api/tide/publication-index.ts index 21561d652e..9294dae254 100644 --- a/packages/ripple-tide-publication/server/api/tide/publication-index.ts +++ b/packages/ripple-tide-publication/server/api/tide/publication-index.ts @@ -56,9 +56,12 @@ class TidePublicationIndexApi extends TideApiBase { async getPublicationMenu(id: string) { try { - const response = await this.get(`/node/publication/${id}/hierarchy`, { - params: { site: this.siteId } - }) + const { data: response } = await this.get( + `/node/publication/${id}/hierarchy`, + { + params: { site: this.siteId } + } + ) const resource = jsonapiParse.parse(response).data.meta.hierarchy const siteData = await this.getMappedData( this.publicationMapping.mapping, From 57c92cd46d2c7a3b3da9f36db862f60d8ab63c66 Mon Sep 17 00:00:00 2001 From: Jeffrey Dowdle Date: Mon, 25 Sep 2023 13:50:03 +1000 Subject: [PATCH 3/3] test(@dpc-sdp/ripple-tide-api): fixed unit tests --- .../src/services/__test__/tide-api-base.test.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/ripple-tide-api/src/services/__test__/tide-api-base.test.ts b/packages/ripple-tide-api/src/services/__test__/tide-api-base.test.ts index efff8e6f82..de7572e9d4 100644 --- a/packages/ripple-tide-api/src/services/__test__/tide-api-base.test.ts +++ b/packages/ripple-tide-api/src/services/__test__/tide-api-base.test.ts @@ -89,11 +89,18 @@ describe('TideApiBase', () => { mockLogger ) it('should call http client get method', async () => { - mockClient.onGet(`${exampleApiConfig.apiPrefix}/site`).reply(200, { - field: 'test' - }) + mockClient.onGet(`${exampleApiConfig.apiPrefix}/site`).reply( + 200, + { + field: 'test' + }, + { + testHeader: 'test123' + } + ) const result = await tideApiBase.get('/site') - expect(result).toEqual({ field: 'test' }) + expect(result.data).toEqual({ field: 'test' }) + expect(result.headers.testHeader).toEqual('test123') mockClient.reset() }) })