Skip to content

Commit

Permalink
Refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
myrotvorets-team committed Sep 26, 2023
1 parent c22de77 commit 614d768
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 26 deletions.
59 changes: 41 additions & 18 deletions src/controllers/search.mts
Original file line number Diff line number Diff line change
@@ -1,26 +1,49 @@
import { type NextFunction, type Request, type Response, Router } from 'express';
import { type NextFunction, type Request, RequestHandler, type Response, Router } from 'express';
import { asyncWrapperMiddleware } from '@myrotvorets/express-async-middleware-wrapper';
import { SearchService } from '../services/search.mjs';
import { type SearchItem, SearchService } from '../services/search.mjs';
import { environment } from '../lib/environment.mjs';

async function searchHandler(req: Request, res: Response, next: NextFunction): Promise<void> {
const result = await SearchService.search(req.query.s as string);
if (result === null) {
next({
success: false,
status: 400,
code: 'BAD_SEARCH_TERM',
message: 'Both surname and name are required',
});
} else {
res.json({
success: true,
items: result,
});
}
type DefaultParams = Record<string, unknown>;

interface SearchRequestParams {
s: string;
}

interface SearchResponse {
success: true;
items: SearchItem[];
}

function searchHandler(
service: SearchService,
): RequestHandler<DefaultParams, SearchResponse, never, SearchRequestParams> {
return asyncWrapperMiddleware(async function _searchHandler(
req: Request<DefaultParams, SearchResponse, never, SearchRequestParams>,
res: Response<SearchResponse>,
next: NextFunction,
): Promise<void> {
const result = await service.search(req.query.s);
if (result === null) {
next({
success: false,
status: 400,
code: 'BAD_SEARCH_TERM',
message: 'Both surname and name are required',
});
} else {
res.json({
success: true,
items: result,
});
}
});
}

export function searchController(): Router {
const router = Router();
router.get('/search', asyncWrapperMiddleware(searchHandler));
const env = environment();
const service = new SearchService(env.IMAGE_CDN_PREFIX);

router.get('/search', searchHandler(service));
return router;
}
4 changes: 3 additions & 1 deletion src/lib/environment.mts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { cleanEnv, port, str } from 'envalid';
import { cleanEnv, port, str, url } from 'envalid';

export interface Environment {
NODE_ENV: string;
PORT: number;
IMAGE_CDN_PREFIX: string;
}

let environ: Environment | null = null;
Expand All @@ -12,6 +13,7 @@ export function environment(reset = false): Environment {
environ = cleanEnv(process.env, {
NODE_ENV: str({ default: 'development' }),
PORT: port({ default: 3000 }),
IMAGE_CDN_PREFIX: url({ default: 'https://cdn.myrotvorets.center/m/' }),
});
}

Expand Down
14 changes: 10 additions & 4 deletions src/services/search.mts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,13 @@ export interface SearchItem {
}

export class SearchService {
public static async search(name: string): Promise<SearchItem[] | null> {
private readonly _cdnPrefix: string;

public constructor(cdnPrefix: string) {
this._cdnPrefix = cdnPrefix;
}

public async search(name: string): Promise<SearchItem[] | null> {
const n = SearchService.prepareName(name);
if (!n) {
return null;
Expand All @@ -35,13 +41,13 @@ export class SearchService {

if (rows) {
const thumbs = SearchService.getThumbnails(atts);
return SearchService.prepareResult(rows, thumbs);
return this.prepareResult(rows, thumbs);
}

return [];
}

private static prepareResult(criminals: Criminal[], thumbs: Record<number, string>): SearchItem[] {
private prepareResult(criminals: Criminal[], thumbs: Record<number, string>): SearchItem[] {
return criminals.map((item) => {
const entry: SearchItem = {
id: item.id,
Expand All @@ -61,7 +67,7 @@ export class SearchService {
}

if (typeof thumbs[item.id] !== 'undefined') {
entry.thumbnail = `https://cdn.myrotvorets.center/m/${thumbs[item.id]}`;
entry.thumbnail = `${this._cdnPrefix}${thumbs[item.id]}`;
}

return entry;
Expand Down
5 changes: 5 additions & 0 deletions test/unit/lib/environment.test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ describe('environment', function () {
const expected: Environment = {
NODE_ENV: 'development',
PORT: 3000,
IMAGE_CDN_PREFIX: 'https://cdn.example.com/',
};

process.env = {
NODE_ENV: `${expected.NODE_ENV}`,
PORT: `${expected.PORT}`,
IMAGE_CDN_PREFIX: `${expected.IMAGE_CDN_PREFIX}`,
EXTRA: 'xxx',
};

Expand All @@ -32,11 +34,13 @@ describe('environment', function () {
const expected: Environment = {
NODE_ENV: 'staging',
PORT: 3030,
IMAGE_CDN_PREFIX: 'https://cdn.example.com/',
};

process.env = {
NODE_ENV: `${expected.NODE_ENV}`,
PORT: `${expected.PORT}`,
IMAGE_CDN_PREFIX: `${expected.IMAGE_CDN_PREFIX}`,
};

let actual = { ...environment(true) };
Expand All @@ -45,6 +49,7 @@ describe('environment', function () {
process.env = {
NODE_ENV: `${expected.NODE_ENV}${expected.NODE_ENV}`,
PORT: `1${expected.PORT}`,
IMAGE_CDN_PREFIX: `${expected.IMAGE_CDN_PREFIX}`,
};

actual = { ...environment() };
Expand Down
9 changes: 6 additions & 3 deletions test/unit/services/search.test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,8 @@ describe('SearchService', function () {
// eslint-disable-next-line mocha/no-setup-in-describe
table1.forEach((name) =>
it(`should return null when prepareName returns falsy value ('${name}')`, function () {
return expect(SearchService.search(name)).to.eventually.be.null;
const svc = new SearchService('https://cdn.example.com/');
return expect(svc.search(name)).to.eventually.be.null;
}),
);

Expand Down Expand Up @@ -167,7 +168,8 @@ describe('SearchService', function () {
});

tracker.install();
return expect(SearchService.search('Путин Владимир')).to.become([]);
const svc = new SearchService('https://cdn.example.com/');
return expect(svc.search('Путин Владимир')).to.become([]);
});

it('should return the expected results', function () {
Expand Down Expand Up @@ -201,7 +203,8 @@ describe('SearchService', function () {
});

tracker.install();
return expect(SearchService.search('Our mock will find everything')).to.become(resultItems);
const svc = new SearchService('https://cdn.myrotvorets.center/m/');
return expect(svc.search('Our mock will find everything')).to.become(resultItems);
});
});
});

0 comments on commit 614d768

Please sign in to comment.