From 195c5cdc5059ba63256c39529f8dad78d86fa1f5 Mon Sep 17 00:00:00 2001 From: yhdev7935 Date: Sun, 7 May 2023 02:33:16 +0900 Subject: [PATCH 1/8] add: article search get method --- whenthen-backend/src/api/index.ts | 1 + whenthen-backend/src/api/search/index.ts | 75 ++++++++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 whenthen-backend/src/api/search/index.ts diff --git a/whenthen-backend/src/api/index.ts b/whenthen-backend/src/api/index.ts index 2bf7d82..75146d3 100644 --- a/whenthen-backend/src/api/index.ts +++ b/whenthen-backend/src/api/index.ts @@ -5,5 +5,6 @@ const router: Router = express.Router(); router.use('/mock', require('./mock')); router.use('/auth', require('./auth')); router.use('/user', require('./user')); +router.use('/search', require('./search')); module.exports = router; diff --git a/whenthen-backend/src/api/search/index.ts b/whenthen-backend/src/api/search/index.ts new file mode 100644 index 0000000..af370be --- /dev/null +++ b/whenthen-backend/src/api/search/index.ts @@ -0,0 +1,75 @@ +import express, { Request, Response, Router } from 'express'; +import { + authProtected, + authUnprotected, + IGetUserAuthInfoRequest, +} from '../../middlewares/auth'; +import HttpStatus from 'http-status-codes'; +import promisePool from '../../db'; + +const router: Router = express.Router(); + +export interface ISearchData { + title?: string; + thumbnail?: Blob; + detail?: string; + url?: string; + place?: string; + start_datetime?: Date; + end_datetime?: Date; + user_id: string; +} + +router.get( + '/', + authProtected, + async (req: IGetUserAuthInfoRequest, res: Response) => { + try { + if ( + req.query.type == 'detail' || + req.query.type == 'title' || + req.query.type == 'start_datetime' || + req.query.type == 'end_datetime' || + req.query.type == 'place' + ) { + if (!req.query.value) + throw new Error( + `${req.query.type} does exist, but value doesn't exist.`, + ); + const [rows, _] = await promisePool.execute( + `SELECT * FROM ARTICLE WHERE ${req.query.type} LIKE '%${req.query.value}%' LIMIT 1000`, + ); + + const data: ISearchData[] = []; + if (rows.length) { + rows.forEach((row: any) => { + const searchData: ISearchData = { + title: row.title, + thumbnail: row.thumbnail, + detail: row.detail, + url: row.url, + place: row.place, + start_datetime: row.start_datetime, + end_datetime: row.end_datetime, + user_id: row.user_id, + }; + data.push(searchData); + }); + } + + return res.status(HttpStatus.OK).json({ + status: HttpStatus.OK, + data: data, + }); + } + throw new Error('Search type is invalid.'); + } catch (err: any) { + return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ + status: HttpStatus.INTERNAL_SERVER_ERROR, + message: err instanceof Error ? err.message : String(err), + }); + } + }, +); + +module.exports = router; From 56acb4ded973a291d3c18e3d8793c9c38a39bfb6 Mon Sep 17 00:00:00 2001 From: yhdev7935 Date: Sun, 7 May 2023 13:17:33 +0900 Subject: [PATCH 2/8] add: pagination in article search feature --- whenthen-backend/src/api/search/index.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/whenthen-backend/src/api/search/index.ts b/whenthen-backend/src/api/search/index.ts index af370be..9a3bc2f 100644 --- a/whenthen-backend/src/api/search/index.ts +++ b/whenthen-backend/src/api/search/index.ts @@ -22,9 +22,13 @@ export interface ISearchData { router.get( '/', - authProtected, + authUnprotected, async (req: IGetUserAuthInfoRequest, res: Response) => { try { + const query_per_page = 10; + let page: number = 1; + if (req.query.page) page = parseInt(req.query.page as string); + if (page <= 0) throw new Error('page cannot be negative or zero!'); if ( req.query.type == 'detail' || req.query.type == 'title' || @@ -37,7 +41,9 @@ router.get( `${req.query.type} does exist, but value doesn't exist.`, ); const [rows, _] = await promisePool.execute( - `SELECT * FROM ARTICLE WHERE ${req.query.type} LIKE '%${req.query.value}%' LIMIT 1000`, + `SELECT * FROM ARTICLE WHERE ${req.query.type} LIKE '%${ + req.query.value + }%' LIMIT ${query_per_page} OFFSET ${(page - 1) * query_per_page}`, ); const data: ISearchData[] = []; @@ -59,6 +65,7 @@ router.get( return res.status(HttpStatus.OK).json({ status: HttpStatus.OK, + page: page, data: data, }); } From 0d332a01dce14a7c493a58326c5afc4634899baf Mon Sep 17 00:00:00 2001 From: yhdev7935 Date: Sun, 7 May 2023 13:20:38 +0900 Subject: [PATCH 3/8] fix: change to authProtected in article search feature --- whenthen-backend/src/api/search/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/whenthen-backend/src/api/search/index.ts b/whenthen-backend/src/api/search/index.ts index 9a3bc2f..7fd0cf9 100644 --- a/whenthen-backend/src/api/search/index.ts +++ b/whenthen-backend/src/api/search/index.ts @@ -22,7 +22,7 @@ export interface ISearchData { router.get( '/', - authUnprotected, + authProtected, async (req: IGetUserAuthInfoRequest, res: Response) => { try { const query_per_page = 10; From e7f8d3a64d772e78c5d90d6d0010ebd31bc8d7f8 Mon Sep 17 00:00:00 2001 From: yhdev7935 Date: Mon, 8 May 2023 15:00:17 +0900 Subject: [PATCH 4/8] add more types in article search --- whenthen-backend/src/api/search/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/whenthen-backend/src/api/search/index.ts b/whenthen-backend/src/api/search/index.ts index 7fd0cf9..a48de39 100644 --- a/whenthen-backend/src/api/search/index.ts +++ b/whenthen-backend/src/api/search/index.ts @@ -30,7 +30,9 @@ router.get( if (req.query.page) page = parseInt(req.query.page as string); if (page <= 0) throw new Error('page cannot be negative or zero!'); if ( + req.query.type == 'title' || req.query.type == 'detail' || + req.query.type == 'url' || req.query.type == 'title' || req.query.type == 'start_datetime' || req.query.type == 'end_datetime' || From dcc1770700419a373311bc1ea03c76ab7badce7b Mon Sep 17 00:00:00 2001 From: yhdev7935 Date: Mon, 8 May 2023 15:12:44 +0900 Subject: [PATCH 5/8] fix: remove useless if condition in article search --- whenthen-backend/src/api/search/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/whenthen-backend/src/api/search/index.ts b/whenthen-backend/src/api/search/index.ts index a48de39..dcbc2fb 100644 --- a/whenthen-backend/src/api/search/index.ts +++ b/whenthen-backend/src/api/search/index.ts @@ -33,7 +33,6 @@ router.get( req.query.type == 'title' || req.query.type == 'detail' || req.query.type == 'url' || - req.query.type == 'title' || req.query.type == 'start_datetime' || req.query.type == 'end_datetime' || req.query.type == 'place' From 4f04fc69dca1a8b396b8853a0674b207fc881709 Mon Sep 17 00:00:00 2001 From: yhdev7935 Date: Tue, 9 May 2023 09:52:30 +0900 Subject: [PATCH 6/8] refactor: improve readability article search code --- whenthen-backend/src/api/search/index.ts | 80 +++++++++++------------- 1 file changed, 38 insertions(+), 42 deletions(-) diff --git a/whenthen-backend/src/api/search/index.ts b/whenthen-backend/src/api/search/index.ts index dcbc2fb..17f4a69 100644 --- a/whenthen-backend/src/api/search/index.ts +++ b/whenthen-backend/src/api/search/index.ts @@ -25,52 +25,48 @@ router.get( authProtected, async (req: IGetUserAuthInfoRequest, res: Response) => { try { - const query_per_page = 10; - let page: number = 1; - if (req.query.page) page = parseInt(req.query.page as string); + const resultsPerPage = 10; + let page: number = parseInt(req.query.page as string) || 1; if (page <= 0) throw new Error('page cannot be negative or zero!'); - if ( - req.query.type == 'title' || - req.query.type == 'detail' || - req.query.type == 'url' || - req.query.type == 'start_datetime' || - req.query.type == 'end_datetime' || - req.query.type == 'place' - ) { - if (!req.query.value) - throw new Error( - `${req.query.type} does exist, but value doesn't exist.`, - ); - const [rows, _] = await promisePool.execute( - `SELECT * FROM ARTICLE WHERE ${req.query.type} LIKE '%${ - req.query.value - }%' LIMIT ${query_per_page} OFFSET ${(page - 1) * query_per_page}`, - ); - const data: ISearchData[] = []; - if (rows.length) { - rows.forEach((row: any) => { - const searchData: ISearchData = { - title: row.title, - thumbnail: row.thumbnail, - detail: row.detail, - url: row.url, - place: row.place, - start_datetime: row.start_datetime, - end_datetime: row.end_datetime, - user_id: row.user_id, - }; - data.push(searchData); - }); - } + const validTypes = [ + 'detail', + 'title', + 'url', + 'start_datetime', + 'end_datetime', + 'place', + ]; - return res.status(HttpStatus.OK).json({ - status: HttpStatus.OK, - page: page, - data: data, - }); + if (!validTypes.includes(req.query.type as string)) { + throw new Error('Search type is invalid.'); } - throw new Error('Search type is invalid.'); + + if (!req.query.value) + throw new Error( + `${req.query.type} does exist, but value doesn't exist.`, + ); + + const offset = (page - 1) * resultsPerPage; + const query = `SELECT * FROM ARTICLE WHERE ${req.query.type} LIKE '%${req.query.value}%' LIMIT ${resultsPerPage} OFFSET ${offset}`; + const [rows, _] = await promisePool.execute(query); + + const data: ISearchData[] = rows.map((row: any) => ({ + title: row.title, + thumbnail: row.thumbnail, + detail: row.detail, + url: row.url, + place: row.place, + start_datetime: row.start_datetime, + end_datetime: row.end_datetime, + user_id: row.user_id, + })); + + return res.status(HttpStatus.OK).json({ + status: HttpStatus.OK, + page: page, + data: data, + }); } catch (err: any) { return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ status: HttpStatus.INTERNAL_SERVER_ERROR, From b6fc104d9144c4380711174d1e47458fc8567f67 Mon Sep 17 00:00:00 2001 From: yhdev7935 Date: Wed, 10 May 2023 21:09:18 +0900 Subject: [PATCH 7/8] fix: prevent SQL Injection in article search --- whenthen-backend/src/api/search/index.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/whenthen-backend/src/api/search/index.ts b/whenthen-backend/src/api/search/index.ts index 17f4a69..85a4cfe 100644 --- a/whenthen-backend/src/api/search/index.ts +++ b/whenthen-backend/src/api/search/index.ts @@ -6,6 +6,7 @@ import { } from '../../middlewares/auth'; import HttpStatus from 'http-status-codes'; import promisePool from '../../db'; +import mysql from 'mysql2'; const router: Router = express.Router(); @@ -48,7 +49,11 @@ router.get( ); const offset = (page - 1) * resultsPerPage; - const query = `SELECT * FROM ARTICLE WHERE ${req.query.type} LIKE '%${req.query.value}%' LIMIT ${resultsPerPage} OFFSET ${offset}`; + const query = `SELECT * FROM ARTICLE WHERE ${mysql.escape( + req.query.type, + )} LIKE '%${mysql.escape(req.query.value)}%' LIMIT ${mysql.escape( + resultsPerPage, + )} OFFSET ${mysql.escape(offset)}`; const [rows, _] = await promisePool.execute(query); const data: ISearchData[] = rows.map((row: any) => ({ From d96071478b858929b70655c4837e9d4cc887df03 Mon Sep 17 00:00:00 2001 From: yhdev7935 Date: Thu, 11 May 2023 00:59:05 +0900 Subject: [PATCH 8/8] fix: sql query escape not working --- whenthen-backend/src/api/search/index.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/whenthen-backend/src/api/search/index.ts b/whenthen-backend/src/api/search/index.ts index 85a4cfe..ba3a872 100644 --- a/whenthen-backend/src/api/search/index.ts +++ b/whenthen-backend/src/api/search/index.ts @@ -48,12 +48,14 @@ router.get( `${req.query.type} does exist, but value doesn't exist.`, ); + const type = mysql.escapeId(req.query.type); + const value = mysql.escape('%' + req.query.value + '%'); + const offset = (page - 1) * resultsPerPage; - const query = `SELECT * FROM ARTICLE WHERE ${mysql.escape( - req.query.type, - )} LIKE '%${mysql.escape(req.query.value)}%' LIMIT ${mysql.escape( + const query = `SELECT * FROM ARTICLE WHERE ${type} LIKE ${value} LIMIT ${mysql.escape( resultsPerPage, )} OFFSET ${mysql.escape(offset)}`; + console.log(query); const [rows, _] = await promisePool.execute(query); const data: ISearchData[] = rows.map((row: any) => ({