Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#487176 added robots.txt implementation #946

Merged
merged 14 commits into from
Mar 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,7 @@ lerna-debug.log
coverage/
/badges
packages/*/*.tgz
samples/
samples/

.idea
.next
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import config from 'temp/config';
import { GraphQLRobotsService } from '@sitecore-jss/sitecore-jss-nextjs';

const robotsApi = async (_req: NextApiRequest, res: NextApiResponse): Promise<void> => {
// Ensure response is text/html
res.setHeader('Content-Type', 'text/html;charset=utf-8');

// create robots graphql service
const robotsService = new GraphQLRobotsService({
endpoint: config.graphQLEndpoint,
apiKey: config.sitecoreApiKey,
siteName: config.jssAppName,
});

const robotsResult = await robotsService.fetchRobots();

return res.status(200).send(robotsResult);
};

export default robotsApi;
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
const robotsPlugin = (nextConfig = {}) => {
return Object.assign({}, nextConfig, {
async rewrites() {
return [
...await nextConfig.rewrites(),
// robots route
{
source: '/robots.txt',
destination: '/api/robots',
},
];
},
});
};

module.exports = robotsPlugin;
5 changes: 5 additions & 0 deletions packages/sitecore-jss-nextjs/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ export {
RestDictionaryService,
RestDictionaryServiceConfig,
} from '@sitecore-jss/sitecore-jss/i18n';
export {
RobotsQueryResult,
GraphQLRobotsService,
GraphQLRobotsServiceConfig,
} from '@sitecore-jss/sitecore-jss/site';
export { GraphQLRequestClient } from '@sitecore-jss/sitecore-jss';

export {
Expand Down
1 change: 1 addition & 0 deletions packages/sitecore-jss/site.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './types/site/index';
1 change: 1 addition & 0 deletions packages/sitecore-jss/site.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('./dist/cjs/site/index');
1 change: 1 addition & 0 deletions packages/sitecore-jss/src/debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@ export default Object.freeze({
dictionary: debug(`${rootNamespace}:dictionary`),
experienceEditor: debug(`${rootNamespace}:editing`),
sitemap: debug(`${rootNamespace}:sitemap`),
robots: debug(`${rootNamespace}:robots`),
});
63 changes: 63 additions & 0 deletions packages/sitecore-jss/src/site/graphql-robots-service.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { expect } from 'chai';
import nock from 'nock';
import { GraphQLRobotsService, siteNameError } from './graphql-robots-service';

const robotsQueryResultNull = {
site: {
siteInfo: null,
},
};

describe('GraphQLRobotsService', () => {
const endpoint = 'http://site';
const apiKey = 'some-api-key';
const siteName = 'site-name';

afterEach(() => {
nock.cleanAll();
});

const mockRobotsRequest = (siteName?: string) => {
nock(endpoint)
.post('/')
.reply(
200,
siteName
? {
data: {
site: {
siteInfo: {
robots: siteName,
},
},
},
}
: {
data: robotsQueryResultNull,
}
);
};

describe('Fetch robots.txt', () => {
it('should get error if robots.txt has empty sitename', async () => {
mockRobotsRequest();

const service = new GraphQLRobotsService({ endpoint, apiKey, siteName: '' });
await service.fetchRobots().catch((error: Error) => {
expect(error.message).to.equal(siteNameError);
});

return expect(nock.isDone()).to.be.false;
});

it('should get robots.txt', async () => {
mockRobotsRequest(siteName);

const service = new GraphQLRobotsService({ endpoint, apiKey, siteName });
const robots = await service.fetchRobots();
expect(robots).to.equal(siteName);

return expect(nock.isDone()).to.be.true;
});
});
});
92 changes: 92 additions & 0 deletions packages/sitecore-jss/src/site/graphql-robots-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { GraphQLClient, GraphQLRequestClient } from '../graphql';
import debug from '../debug';

// The default query for request robots.txt
const defaultQuery = /* GraphQL */ `
query RobotsQuery($siteName: String!) {
site {
siteInfo(site: $siteName) {
robots
}
}
}
`;

/** @private */
export const siteNameError = 'The siteName cannot be empty';

export type GraphQLRobotsServiceConfig = {
/**
* 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 robots.txt request
*/
export type RobotsQueryResult = { site: { siteInfo: { robots: string } } };

/**
* Service that fetch the robots.txt data using Sitecore's GraphQL API.
*/
export class GraphQLRobotsService {
private graphQLClient: GraphQLClient;

protected get query(): string {
return defaultQuery;
}

/**
* Creates an instance of graphQL robots.txt service with the provided options
* @param {GraphQLRobotsServiceConfig} options instance
*/
constructor(public options: GraphQLRobotsServiceConfig) {
this.graphQLClient = this.getGraphQLClient();
}

/**
* Fetch a data of robots.txt from API
* @returns text of robots.txt
* @throws {Error} if the siteName is empty.
*/
async fetchRobots(): Promise<string> {
const siteName: string = this.options.siteName;

if (!siteName) {
throw new Error(siteNameError);
}

const robotsResult: Promise<RobotsQueryResult> = this.graphQLClient.request(this.query, {
siteName,
});
try {
return robotsResult.then((result: RobotsQueryResult) => {
return result?.site?.siteInfo?.robots;
});
} catch (e) {
return Promise.reject(e);
}
}

/**
* 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.robots,
});
}
}
5 changes: 5 additions & 0 deletions packages/sitecore-jss/src/site/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export {
RobotsQueryResult,
GraphQLRobotsService,
GraphQLRobotsServiceConfig,
} from './graphql-robots-service';
1 change: 1 addition & 0 deletions packages/sitecore-jss/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"graphql.d.ts",
"i18n.d.ts",
"tracking.d.ts",
"site.d.ts",
"src/**/*.test.ts",
"src/testData/*",
]
Expand Down