feat(api): allow redirection with message (#50525)

Co-authored-by: Muhammed Mustafa <MuhammedElruby@gmail.com>
This commit is contained in:
Oliver Eyton-Williams
2023-06-07 15:30:12 +02:00
committed by GitHub
parent 962f45475c
commit 65239ee68b
4 changed files with 161 additions and 2 deletions
+3 -2
View File
@@ -5,8 +5,8 @@
},
"dependencies": {
"@fastify/cookie": "^8.3.0",
"@fastify/middie": "8.3",
"@fastify/csrf-protection": "6.3.0",
"@fastify/middie": "8.3",
"@fastify/session": "^10.1.1",
"@fastify/swagger": "^8.3.1",
"@fastify/swagger-ui": "^1.5.0",
@@ -17,7 +17,8 @@
"fastify": "4.17.0",
"fastify-auth0-verify": "^1.0.0",
"fastify-plugin": "^4.3.0",
"nodemon": "2.0.22"
"nodemon": "2.0.22",
"query-string": "^7.1.3"
},
"description": "The freeCodeCamp.org open-source codebase and curriculum",
"devDependencies": {
@@ -0,0 +1,101 @@
import Fastify, { FastifyInstance } from 'fastify';
import qs from 'query-string';
import redirectWithMessage from './redirect-with-message';
async function setupServer() {
const fastify = Fastify();
await fastify.register(redirectWithMessage);
return fastify;
}
const isString = (value: unknown): value is string => {
return typeof value === 'string';
};
describe('redirectWithMessage plugin', () => {
it('should decorate reply object with redirectWithMessage method', async () => {
expect.assertions(3);
const fastify = await setupServer();
fastify.get('/', (_req, reply) => {
expect(reply).toHaveProperty('redirectWithMessage');
expect(reply.redirectWithMessage).toBeInstanceOf(Function);
return { foo: 'bar' };
});
const res = await fastify.inject({
method: 'GET',
url: '/'
});
expect(res.statusCode).toEqual(200);
});
describe('redirectWithMessage method', () => {
let fastify: FastifyInstance;
beforeEach(async () => {
fastify = await setupServer();
});
it('should redirect to the first argument', async () => {
fastify.get('/', (_req, reply) => {
return reply.redirectWithMessage('/target', {
type: 'info',
content: 'foo'
});
});
const res = await fastify.inject({
method: 'GET',
url: '/'
});
expect(res.headers.location).toMatch(/^\/target/);
expect(res.statusCode).toEqual(302);
});
it('should convert the second argument into a query string', async () => {
fastify.get('/', (_req, reply) => {
return reply.redirectWithMessage('/target', {
type: 'info',
content: 'foo'
});
});
const res = await fastify.inject({
method: 'GET',
url: '/'
});
expect(res.headers.location).toMatch(/^\/target\?messages=info/);
});
it('should encode the message twice when creating the query string', async () => {
const expectedMessage = { danger: ['foo bar'] };
fastify.get('/', (_req, reply) => {
return reply.redirectWithMessage('/target', {
type: 'danger',
content: 'foo bar'
});
});
const res = await fastify.inject({
method: 'GET',
url: '/'
});
if (!isString(res.headers.location))
throw new Error('Location is not a string');
const { search } = new URL(res.headers.location, 'http://localhost');
// The query string itself is encoded:
const { messages } = qs.parse(search, { arrayFormat: 'index' });
if (!isString(messages)) throw new Error('Messages is not a string');
// As is the message embedded in it:
expect(qs.parse(messages, { arrayFormat: 'index' })).toEqual(
expectedMessage
);
});
});
});
+54
View File
@@ -0,0 +1,54 @@
import { FastifyPluginCallback, type FastifyReply } from 'fastify';
import fp from 'fastify-plugin';
// TODO: (POST MVP)use node's querystring and just JSON stringify the message.
// No need for query-string on either side.
import qs from 'query-string';
declare module 'fastify' {
interface FastifyReply {
redirectWithMessage: typeof redirectWithMessage;
}
}
type Message = {
type: 'info' | 'danger' | 'success' | 'errors';
content: string;
};
type MessageQuery = {
info?: string[];
danger?: string[];
success?: string[];
errors?: string[];
};
// The client expects a message like { info: ['foo'] }, { danger: ['bar'] } etc.
const prepareMessage = (message: Message): MessageQuery => ({
[message.type]: [message.content]
});
function redirectWithMessage(
this: FastifyReply,
url: string,
message: Message
) {
return this.redirect(
`${url}?${qs.stringify(
{
messages: qs.stringify(prepareMessage(message), {
arrayFormat: 'index'
})
},
{ arrayFormat: 'index' }
)}`
);
}
const plugin: FastifyPluginCallback = (fastify, _options, done) => {
fastify.decorateReply('redirectWithMessage', redirectWithMessage);
done();
};
export default fp(plugin);
+3
View File
@@ -213,6 +213,9 @@ importers:
nodemon:
specifier: 2.0.22
version: 2.0.22
query-string:
specifier: ^7.1.3
version: 7.1.3
devDependencies:
'@fastify/type-provider-typebox':
specifier: 3.2.0