-
Notifications
You must be signed in to change notification settings - Fork 38
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: back-end implementation of ranked link seach (#210)
* feat: endpoint for url search * fix: remove redundant log * fix: inappropriate error message * feat: rate limiting on search endpoint * refactor: use table name from orm * feat: hide link clicks from search response * feat: support different search orders * fix: update comments * fix: imports * fix: search order validation * feat: add unit test for search controller * fix: test request using wrong params * feat: search ignores inactive links * refactor: move stripping of clicks to service layer * feat: additional tests for new methods * feat: add more tests for textsearch * refactor: remove redundant coalesce * fix: error in sql statement for recency sort * docs: add comment explaining ts_rank_cd normalization * refactor: extract helper methods from search * fix: packagelock * fix: use more reasonable default limit * fix: typo in documentation * refactor: capitalize sql keywords * fix: count including inactive urls and not using index * feat: rate limit use real ip and logs when limit is reached * fix: formatting
- Loading branch information
1 parent
cb00700
commit 178d163
Showing
22 changed files
with
810 additions
and
19 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
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
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,43 @@ | ||
import Express from 'express' | ||
import { createValidator } from 'express-joi-validation' | ||
import Joi from '@hapi/joi' | ||
import rateLimit from 'express-rate-limit' | ||
import { container } from '../util/inversify' | ||
import { DependencyIds } from '../constants' | ||
import { SearchControllerInterface } from '../controllers/interfaces/SearchControllerInterface' | ||
import { SearchResultsSortOrder } from '../repositories/enums' | ||
import getIp from '../util/request' | ||
import { logger } from '../config' | ||
|
||
const urlSearchRequestSchema = Joi.object({ | ||
query: Joi.string().required(), | ||
order: Joi.string() | ||
.required() | ||
.allow(...Object.values(SearchResultsSortOrder)) | ||
.only(), | ||
limit: Joi.number(), | ||
offset: Joi.number(), | ||
}) | ||
|
||
const apiLimiter = rateLimit({ | ||
keyGenerator: (req) => getIp(req) as string, | ||
onLimitReached: (req) => | ||
logger.warn(`Rate limit reached for IP Address: ${getIp(req)}`), | ||
windowMs: 1000, // 1 second | ||
max: 20, | ||
}) | ||
|
||
const router = Express.Router() | ||
const validator = createValidator() | ||
const searchController = container.get<SearchControllerInterface>( | ||
DependencyIds.searchController, | ||
) | ||
|
||
router.get( | ||
'/urls', | ||
apiLimiter, | ||
validator.query(urlSearchRequestSchema), | ||
searchController.urlSearchPlainText, | ||
) | ||
|
||
module.exports = router |
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 |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import { inject, injectable } from 'inversify' | ||
import Express from 'express' | ||
import { UrlSearchServiceInterface } from '../services/interfaces/UrlSearchServiceInterface' | ||
import { DependencyIds } from '../constants' | ||
import { logger } from '../config' | ||
import jsonMessage from '../util/json' | ||
import { SearchControllerInterface } from './interfaces/SearchControllerInterface' | ||
import { SearchResultsSortOrder } from '../repositories/enums' | ||
|
||
type UrlSearchRequest = { | ||
query: string | ||
order: string | ||
limit?: number | ||
offset?: number | ||
} | ||
|
||
@injectable() | ||
export class SearchController implements SearchControllerInterface { | ||
private urlSearchService: UrlSearchServiceInterface | ||
|
||
public constructor( | ||
@inject(DependencyIds.urlSearchService) | ||
urlSearchService: UrlSearchServiceInterface, | ||
) { | ||
this.urlSearchService = urlSearchService | ||
} | ||
|
||
public urlSearchPlainText: ( | ||
req: Express.Request, | ||
res: Express.Response, | ||
) => Promise<void> = async (req, res) => { | ||
const { | ||
query, | ||
order, | ||
limit = 100, | ||
offset = 0, | ||
} = req.query as UrlSearchRequest | ||
|
||
try { | ||
const { urls, count } = await this.urlSearchService.plainTextSearch( | ||
query, | ||
order as SearchResultsSortOrder, | ||
limit, | ||
offset, | ||
) | ||
|
||
res.ok({ | ||
urls, | ||
count, | ||
}) | ||
return | ||
} catch (error) { | ||
logger.error(`Error searching urls: ${error}`) | ||
res.serverError(jsonMessage('Error retrieving URLs for search')) | ||
} | ||
} | ||
} | ||
|
||
export default SearchController |
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
5 changes: 5 additions & 0 deletions
5
src/server/controllers/interfaces/SearchControllerInterface.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,5 @@ | ||
import Express from 'express' | ||
|
||
export interface SearchControllerInterface { | ||
urlSearchPlainText(req: Express.Request, res: Express.Response): Promise<void> | ||
} |
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,12 @@ | ||
-- This migration script adds the index for GoSearch | ||
|
||
DROP INDEX IF EXISTS urls_weighted_search_idx; | ||
|
||
-- Search will be run on a concatenation of vectors formed from short links and their | ||
-- description. Descriptions are given a lower weight than short links as short link | ||
-- words can be taken as the title and words there are likely to be more important than | ||
-- those in their corresponding description. | ||
-- Search queries will have to use this exact expresion to be able to utilize the index. | ||
CREATE INDEX urls_weighted_search_idx ON urls USING gin ((setweight(to_tsvector( | ||
'english', urls."shortUrl"), 'A') || setweight(to_tsvector('english', | ||
urls."description"), 'B'))) where urls.state = 'ACTIVE'; |
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
Oops, something went wrong.