-
Notifications
You must be signed in to change notification settings - Fork 277
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feature/487176 implementation sitemap (#991)
* #487176 added sitemap middleware * #487176 implemented sitemap middleware * #487176 refactoring sitemap service * #487176 refactoring and added unit test for sitemap service * #487176 refactoring * #487176 removed deprecated package(request)
- Loading branch information
1 parent
a40db2a
commit 723a382
Showing
13 changed files
with
273 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,4 +2,4 @@ | |
"dependencies": { | ||
"bootstrap": "^5.1.3" | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
16 changes: 16 additions & 0 deletions
16
packages/create-sitecore-jss/src/templates/nextjs-sxa/src/lib/next-config/plugins/sitemap.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
const sitemapPlugin = (nextConfig = {}) => { | ||
return Object.assign({}, nextConfig, { | ||
async rewrites() { | ||
return [ | ||
...await nextConfig.rewrites(), | ||
// sitemap route | ||
{ | ||
source: '/sitemap:id([\\w-]{0,}).xml', | ||
destination: '/api/sitemap' | ||
}, | ||
]; | ||
}, | ||
}); | ||
}; | ||
|
||
module.exports = sitemapPlugin; |
37 changes: 37 additions & 0 deletions
37
packages/create-sitecore-jss/src/templates/nextjs-sxa/src/pages/api/sitemap.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { AxiosResponse } from 'axios'; | ||
import type { NextApiRequest, NextApiResponse } from 'next'; | ||
import config from 'temp/config'; | ||
import { GraphQLSitemapService } from '@sitecore-jss/sitecore-jss/site'; | ||
import { AxiosDataFetcher } from '@sitecore-jss/sitecore-jss'; | ||
|
||
const ABSOLUTE_URL_REGEXP = '^(?:[a-z]+:)?//'; | ||
|
||
const sitemapApi = async (req: NextApiRequest, res: NextApiResponse): Promise<NextApiResponse | void> => { | ||
const { query: { id } } = req; | ||
// create sitemap graphql service | ||
const sitemapService = new GraphQLSitemapService({ | ||
endpoint: config.graphQLEndpoint, | ||
apiKey: config.sitecoreApiKey, | ||
siteName: config.jssAppName, | ||
}); | ||
|
||
const sitemapPath = await sitemapService.getSitemap(id as string); | ||
|
||
if (sitemapPath) { | ||
const isAbsoluteUrl = sitemapPath.match(ABSOLUTE_URL_REGEXP); | ||
const sitemapUrl = isAbsoluteUrl ? sitemapPath : `${config.sitecoreApiHost}${sitemapPath}`; | ||
res.setHeader('Content-Type', 'text/xml;charset=utf-8'); | ||
|
||
return new AxiosDataFetcher().get(sitemapUrl, { | ||
responseType: 'stream', | ||
}) | ||
.then((response: AxiosResponse) => { | ||
response.data.pipe(res); | ||
}) | ||
.catch(() => res.redirect('/404')); | ||
} | ||
|
||
res.redirect('/404'); | ||
}; | ||
|
||
export default sitemapApi; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './types/edge/index'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
module.exports = require('./dist/cjs/edge/index'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { RedirectsMiddleware } from './redirects-middleware'; |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,6 +30,7 @@ | |
"typings", | ||
"dist", | ||
"middleware.d.ts", | ||
"edge.d.ts", | ||
"src/tests/*", | ||
"src/testData/*", | ||
"**/*.test.ts", | ||
|
105 changes: 105 additions & 0 deletions
105
packages/sitecore-jss/src/site/graphql-sitemap-service.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
import { expect } from 'chai'; | ||
import nock from 'nock'; | ||
import { GraphQLSitemapService } from './graphql-sitemap-service'; | ||
import { siteNameError } from '../constants'; | ||
|
||
const sitemapQueryResultNull = { | ||
site: { | ||
siteInfo: null, | ||
}, | ||
}; | ||
|
||
describe('GraphQLSitemapService', () => { | ||
const endpoint = 'http://site'; | ||
const apiKey = 'some-api-key'; | ||
const siteName = 'site-name'; | ||
const mockSitemap = ['sitemap.xml']; | ||
const mockSitemaps = ['sitemap-1.xml', 'sitemap-2.xml', 'sitemap-3.xml']; | ||
|
||
afterEach(() => { | ||
nock.cleanAll(); | ||
}); | ||
|
||
const mockSitemapRequest = (sitemapUrls?: string[]) => { | ||
nock(endpoint) | ||
.post('/') | ||
.reply( | ||
200, | ||
siteName | ||
? { | ||
data: { | ||
site: { | ||
siteInfo: { | ||
sitemap: sitemapUrls, | ||
}, | ||
}, | ||
}, | ||
} | ||
: { | ||
data: sitemapQueryResultNull, | ||
} | ||
); | ||
}; | ||
|
||
describe('Fetch sitemap', () => { | ||
it('should get error if sitemap has empty sitename', async () => { | ||
mockSitemapRequest(); | ||
|
||
const service = new GraphQLSitemapService({ endpoint, apiKey, siteName: '' }); | ||
await service.fetchSitemaps().catch((error: Error) => { | ||
expect(error.message).to.equal(siteNameError); | ||
}); | ||
|
||
return expect(nock.isDone()).to.be.false; | ||
}); | ||
|
||
it('should fetch sitemap', async () => { | ||
mockSitemapRequest(mockSitemap); | ||
|
||
const service = new GraphQLSitemapService({ endpoint, apiKey, siteName }); | ||
const sitemaps = await service.fetchSitemaps(); | ||
|
||
expect(sitemaps.length).to.equal(1); | ||
expect(sitemaps).to.deep.equal(mockSitemap); | ||
|
||
return expect(nock.isDone()).to.be.true; | ||
}); | ||
|
||
it('should fetch sitemaps', async () => { | ||
mockSitemapRequest(mockSitemaps); | ||
|
||
const service = new GraphQLSitemapService({ endpoint, apiKey, siteName }); | ||
const sitemaps = await service.fetchSitemaps(); | ||
|
||
expect(sitemaps.length).to.equal(3); | ||
expect(sitemaps).to.deep.equal(mockSitemaps); | ||
|
||
return expect(nock.isDone()).to.be.true; | ||
}); | ||
|
||
it('should get exists sitemap', async () => { | ||
const mockIdSitemap = '-3'; | ||
mockSitemapRequest(mockSitemaps); | ||
|
||
const service = new GraphQLSitemapService({ endpoint, apiKey, siteName }); | ||
const sitemap = await service.getSitemap(mockIdSitemap); | ||
|
||
expect(sitemap).to.deep.equal(mockSitemaps[2]); | ||
|
||
return expect(nock.isDone()).to.be.true; | ||
}); | ||
|
||
it('should get null if sitemap not exists', async () => { | ||
const mockIdSitemap = '-5'; | ||
mockSitemapRequest(mockSitemaps); | ||
|
||
const service = new GraphQLSitemapService({ endpoint, apiKey, siteName }); | ||
const sitemap = await service.getSitemap(mockIdSitemap); | ||
|
||
// eslint-disable-next-line no-unused-expressions | ||
expect(sitemap).to.be.undefined; | ||
|
||
return expect(nock.isDone()).to.be.true; | ||
}); | ||
}); | ||
}); |
102 changes: 102 additions & 0 deletions
102
packages/sitecore-jss/src/site/graphql-sitemap-service.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
import { GraphQLClient, GraphQLRequestClient } from '../graphql'; | ||
import { siteNameError } from '../constants'; | ||
import debug from '../debug'; | ||
|
||
const PREFIX_NAME_SITEMAP = 'sitemap'; | ||
|
||
// The default query for request sitemaps | ||
const defaultQuery = /* GraphQL */ ` | ||
query SitemapQuery($siteName: String!) { | ||
site { | ||
siteInfo(site: $siteName) { | ||
sitemap | ||
} | ||
} | ||
} | ||
`; | ||
|
||
export type GraphQLSitemapServiceConfig = { | ||
/** | ||
* Your Graphql endpoint | ||
*/ | ||
endpoint: string; | ||
/** | ||
* The API key to use for authentication | ||
*/ | ||
apiKey: string; | ||
/** | ||
* The JSS application name | ||
*/ | ||
siteName: string; | ||
}; | ||
|
||
/** | ||
* The schema of data returned in response to sitemaps request | ||
*/ | ||
export type SitemapQueryResult = { site: { siteInfo: { sitemap: string[] } } }; | ||
|
||
/** | ||
* Service that fetch the sitemaps data using Sitecore's GraphQL API. | ||
*/ | ||
export class GraphQLSitemapService { | ||
private graphQLClient: GraphQLClient; | ||
|
||
protected get query(): string { | ||
return defaultQuery; | ||
} | ||
|
||
/** | ||
* Creates an instance of graphQL sitemaps service with the provided options | ||
* @param {GraphQLSitemapServiceConfig} options instance | ||
*/ | ||
constructor(public options: GraphQLSitemapServiceConfig) { | ||
this.graphQLClient = this.getGraphQLClient(); | ||
} | ||
|
||
/** | ||
* Fetch list of sitemaps for the site | ||
* @returns {string[]} list of sitemap paths | ||
* @throws {Error} if the siteName is empty. | ||
*/ | ||
async fetchSitemaps(): Promise<string[]> { | ||
const siteName: string = this.options.siteName; | ||
|
||
if (!siteName) { | ||
throw new Error(siteNameError); | ||
} | ||
|
||
const sitemapResult: Promise<SitemapQueryResult> = this.graphQLClient.request(this.query, { | ||
siteName, | ||
}); | ||
try { | ||
return sitemapResult.then((result: SitemapQueryResult) => result.site.siteInfo.sitemap); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
} | ||
|
||
/** | ||
* Get sitemap file path for sitemap id | ||
* @param {string} id the sitemap id (can be empty for default 'sitemap.xml' file) | ||
* @returns {string | undefined} the sitemap file path or undefined if one doesn't exist | ||
*/ | ||
async getSitemap(id: string): Promise<string | undefined> { | ||
const searchSitemap = `${PREFIX_NAME_SITEMAP}${id}.xml`; | ||
const sitemaps = await this.fetchSitemaps(); | ||
|
||
return sitemaps.find((sitemap: string) => sitemap.includes(searchSitemap)); | ||
} | ||
|
||
/** | ||
* Gets a GraphQL client that can make requests to the API. Uses graphql-request as the default | ||
* library for fetching graphql data (@see GraphQLRequestClient). Override this method if you | ||
* want to use something else. | ||
* @returns {GraphQLClient} implementation | ||
*/ | ||
protected getGraphQLClient(): GraphQLClient { | ||
return new GraphQLRequestClient(this.options.endpoint, { | ||
apiKey: this.options.apiKey, | ||
debugger: debug.sitemap, | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters