Skip to content

Commit

Permalink
[BC] refactor: Improve caching by using GET HTTP method instead of POST
Browse files Browse the repository at this point in the history
  • Loading branch information
bartoszputek committed Sep 16, 2024
1 parent a65083f commit c7ba1f9
Show file tree
Hide file tree
Showing 7 changed files with 83 additions and 36 deletions.
11 changes: 6 additions & 5 deletions frontend/scripts/utils/ApiProxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,19 @@ import { API_URL } from '../constants';

export default class ApiProxy {
async getResults(board) {
const data = await this.postData(API_URL, { board });
const data = await this.getData(`${API_URL}/compute`, board);

return data;
}

async postData(url = '', data = {}) {
const response = await fetch(url, {
method: 'POST',
async getData(url = '', data = {}) {
const response = await fetch(`${url}?${new URLSearchParams({
board: btoa(JSON.stringify(data)),
}).toString()}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});

if (response.ok) {
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "poker-master-tool",
"version": "1.0.7",
"version": "2.0.0",
"description": "Open-source poker hand calculator",
"main": "dist/index.js",
"scripts": {
Expand Down Expand Up @@ -67,4 +67,4 @@
"webpack-cli": "^4.10.0",
"webpack-dev-server": "^4.11.1"
}
}
}
2 changes: 1 addition & 1 deletion src/App.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class App {
const computeHandController = new ComputeHandController(cacheInMemory);
this.computeHandHandler = new ComputeHandHandler(computeHandController);

this.express.post('/', this.computeHandHandler.validate, this.computeHandHandler.handle);
this.express.get('/compute', this.computeHandHandler.validate, this.computeHandHandler.handle);

const errorHandler = new ErrorHandler();

Expand Down
39 changes: 34 additions & 5 deletions src/handlers/ComputeHandHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,19 @@ import {
} from 'interfaces';
import BoardValidator from 'validators/BoardValidator';
import Board from 'models/Board';
import ValidationError from '../errors/ValidationError';

export const SECONDS_IN_MONTH: number = 60 * 60 * 24 * 30;

export default class ComputeHandHandler implements IHandler {
constructor(
private readonly controller: IController<IComputeHandParams, Promise<IComputeHandResponse>>,
private readonly boardValidator: IValidator = new BoardValidator(),
) { }

public handle: RequestHandler = async (req: Request, res: Response) => {
const { board: serializedBoard }: { board: ISerializedBoard } = req.body;
public handle: RequestHandler<any, any, any, any, { board: ISerializedBoard, logger: ILogger }> =
async (req: Request, res: Response<any, { board: ISerializedBoard, logger: ILogger }>) => {
const { board: serializedBoard }: { board: ISerializedBoard } = res.locals;

const logger: ILogger = res.locals.logger as ILogger;

Expand All @@ -38,16 +42,41 @@ export default class ComputeHandHandler implements IHandler {
statusCode: 200,
});

res.set('Cache-Control', 'public, max-age=31557600');
res.set('Cache-Control', `public, max-age=${SECONDS_IN_MONTH}`);
res.set('Content-Type', 'application/json');

res.send(response);
};

public validate: RequestHandler = (req: Request, res: Response, next: NextFunction) => {
const { board: serializedBoard }: { board: ISerializedBoard } = req.body;
public validate: RequestHandler<any, any, any, { board: string }> = (
req: Request<any, any, any, { board: string }>,
res: Response,
next: NextFunction,
) => {
const { board }: { board: string } = req.query;

if (typeof board !== 'string') {
throw new ValidationError('The board query parameter is not a string.', { board });
}

const serializedBoard: ISerializedBoard = this._deseralizeBase64Json(board);

this.boardValidator.validate(serializedBoard);

res.locals.board = serializedBoard;

next();
};

private _deseralizeBase64Json(board: string): ISerializedBoard {
const base64Value: string = Buffer.from(board, 'base64').toString('ascii');

try {
const json = JSON.parse(base64Value);

return json;
} catch (error) {
throw new ValidationError('The board query parameter is not a parsable JSON string.', { board });
}
}
}
26 changes: 13 additions & 13 deletions src/interfaces.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
import { ErrorRequestHandler, RequestHandler } from 'express';

export interface IHandler{
handle: RequestHandler | ErrorRequestHandler;
export interface IHandler {
handle: RequestHandler<any, any, any, any, any> | ErrorRequestHandler;
}

export interface IController<Arguments, Response>{
execute(arg:Arguments): Response
export interface IController<Arguments, Response> {
execute(arg: Arguments): Response
}

export interface IValidator{
validate(arg:unknown): boolean
export interface IValidator {
validate(arg: unknown): boolean
}

export type LoggerMessage = Record<string, unknown> | string;

export interface ILogger {
fatal(message:LoggerMessage):void,
error(message:LoggerMessage):void,
warn(message:LoggerMessage):void,
info(message:LoggerMessage):void,
debug(message:LoggerMessage):void,
trace(message:LoggerMessage):void,
timeInfo(message:LoggerMessage):void
fatal(message: LoggerMessage): void,
error(message: LoggerMessage): void,
warn(message: LoggerMessage): void,
info(message: LoggerMessage): void,
debug(message: LoggerMessage): void,
trace(message: LoggerMessage): void,
timeInfo(message: LoggerMessage): void
}

export interface ICache<K, V> {
Expand Down
6 changes: 4 additions & 2 deletions tests/App.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ beforeAll(async () => {
};
});

test('should return response', async () => {
test('GET:/compute: should return response', async () => {
const { app } = context;

const players: ISerializedPlayer[] = [
Expand Down Expand Up @@ -52,7 +52,9 @@ test('should return response', async () => {
deathCards: ['7d'],
};

const response = await request(app).post('/').send({ board });
const encodedBoard: string = Buffer.from(JSON.stringify(board)).toString('base64');

const response = await request(app).get('/compute').query(`board=${encodedBoard}`);

expect(response.statusCode).toBe(200);
expect(response.body).toMatchObject(getExampleResults());
Expand Down
31 changes: 23 additions & 8 deletions tests/handlers/ComputeHandHandler.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { NextFunction, Request, Response } from 'express';

import { IComputeHandParams, IComputeHandResponse } from 'controllers/ComputeHandController';
import ComputeHandHandler from 'handlers/ComputeHandHandler';
import ComputeHandHandler, { SECONDS_IN_MONTH } from 'handlers/ComputeHandHandler';
import Board from 'models/Board';
import { IController, ILogger, ISerializedBoard, IValidator } from 'interfaces';
import { getExampleResults } from '../_utils/Utils';
Expand All @@ -13,8 +13,8 @@ interface ITestContext {
boardValidatorStub: IValidator
seralizedBoard: ISerializedBoard,
parameters: IComputeHandParams,
req: Request,
res: Response,
req: Request<any, any, any, any, { board: ISerializedBoard, logger: ILogger }>,
res: Response<any, { board: ISerializedBoard, logger: ILogger }>,
next: NextFunction
}

Expand Down Expand Up @@ -53,11 +53,13 @@ beforeEach(() => {
logger: loggerStub,
};

const encodedBoard: string = Buffer.from(JSON.stringify(seralizedBoard)).toString('base64');

const req: Partial<Request> = {
body: {
board: seralizedBoard,
query: {
board: encodedBoard,
},
} as Request;
} as Request<any, any, any, any, { board: ISerializedBoard, logger: ILogger }>;
const res: Partial<Response> = {
status: jest.fn(),
send: jest.fn(),
Expand All @@ -74,21 +76,34 @@ beforeEach(() => {
boardValidatorStub,
seralizedBoard,
parameters,
req: req as Request,
res: res as Response,
req: req as Request<any, any, any, any, { board: ISerializedBoard, logger: ILogger }>,
res: res as Response<any, { board: ISerializedBoard, logger: ILogger }>,
next,
};
});

test('handle(): should call execute method and send response', async () => {
const { computeHandHandler, computeHandControllerStub, parameters, req, res, next } = context;

computeHandHandler.validate(req, res, next);

await computeHandHandler.handle(req, res, next);

expect(computeHandControllerStub.execute).toBeCalledWith(parameters);
expect(res.send).toBeCalledWith(getExampleResults());
});

test('handle(): should set cache and content-type headers', async () => {
const { computeHandHandler, req, res, next } = context;

computeHandHandler.validate(req, res, next);

await computeHandHandler.handle(req, res, next);

expect(res.set).toBeCalledWith('Cache-Control', `public, max-age=${SECONDS_IN_MONTH}`);
expect(res.set).toBeCalledWith('Content-Type', 'application/json');
});

test('validate(): should call validation methods and next function', async () => {
const { computeHandHandler, boardValidatorStub, seralizedBoard, req, res, next } = context;

Expand Down

0 comments on commit c7ba1f9

Please sign in to comment.