-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ft(search functionality): User can search trip requests by preference
- Add tests - Add verify token validation - Add user is verified validation - Add validate query parameters validation middleware - Add search query parameter query function - Add search controller - Add route API documentation [Finishes #169258648]
- Loading branch information
Showing
10 changed files
with
355 additions
and
13 deletions.
There are no files selected for viewing
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,28 @@ | ||
import Response from '../helpers/Response'; | ||
import SearchService from '../services/SearchService'; | ||
|
||
|
||
const { searchByPreference } = SearchService; | ||
/** | ||
* @exports | ||
* @class SearchController | ||
*/ | ||
class SearchController { | ||
/** | ||
* User can search trips by preference | ||
* @static | ||
* @param {object} req request object | ||
* @param {object} res response object | ||
* @memberof SearchController | ||
* @returns {object} data | ||
*/ | ||
static async searchedByPreference(req, res) { | ||
const searchedTrips = await searchByPreference(req); | ||
const searchedTripsMap = searchedTrips.filter(request => request.tripRequest !== null); | ||
if (searchedTripsMap.length === 0) { | ||
return Response.errorMessage(req, res, 'Oooops! No trips matching this search query parameter were found!', 404); | ||
} | ||
return Response.successMessage(req, res, 'Successfully retrieved trip requests by that search query parameter', searchedTripsMap, 200); | ||
} | ||
} | ||
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import Response from '../helpers/Response'; | ||
import UserService from '../services/UserService'; | ||
|
||
const { getAUser } = UserService; | ||
|
||
/** | ||
* @export | ||
* @class SearchMiddleware | ||
*/ | ||
class SearchMiddleware { | ||
/** | ||
* check if user exists | ||
* @static | ||
* @param {Object} req request object | ||
* @param {Object} res response object | ||
* @param {Function} next next function | ||
* @returns {Object} if an error exists, returns a bad request error response | ||
*/ | ||
static async checkIfUserExists(req, res, next) { | ||
const { userName } = req.params; | ||
const user = await getAUser(userName); | ||
if (!user.length) { | ||
return Response.errorMessage(req, res, 'Oops! user doesn\'t exist', 404); | ||
} | ||
return next(); | ||
} | ||
} | ||
|
||
export default SearchMiddleware; |
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,28 @@ | ||
import Response from '../helpers/Response'; | ||
|
||
const validateSearchQueries = (req, res, next) => { | ||
const { query } = req; | ||
const allKeys = Object.keys(query); | ||
if (allKeys.length === 0) { | ||
return Response.errorMessage(req, res, 'Please provide a search query key parameter', 400); | ||
} | ||
|
||
const searchKeys = allKeys.map(key => { | ||
if (key === 'originId' || key === 'destinationId' || key === 'startDate' || key === 'returnDate' || key === 'firstName' || key === 'status' || key === 'tripType') { | ||
return true; | ||
} | ||
return false; | ||
}); | ||
|
||
const invalidKeys = searchKeys.filter(key => key === false); | ||
if (invalidKeys.length > 0) { | ||
return Response.errorMessage(req, res, 'You provided an invalid search query key(s) parameter.Your search key should be either originId, destinationId, startDate, returnDate, firstName, status, or tripType', 400); | ||
} | ||
|
||
if (query.originId === '' || query.destinationId === '' || query.startDate === '' || query.returnDate === '' || query.firstName === '' || query.tripType === '') { | ||
return Response.errorMessage(req, res, 'Please provide a search value parameter', 400); | ||
} | ||
return next(); | ||
}; | ||
|
||
export default validateSearchQueries; |
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,62 @@ | ||
import express from 'express'; | ||
import verifyToken from '../../middlewares/verifyToken'; | ||
import isUserVerified from '../../middlewares/isUserVerified'; | ||
import SearchController from '../../controllers/SearchController'; | ||
import validateSearchQueries from '../../middlewares/validateSearchQueries'; | ||
|
||
const searchRoute = express.Router(); | ||
const { searchedByPreference } = SearchController; | ||
|
||
/** | ||
* @swagger | ||
* | ||
* /search: | ||
* get: | ||
* summary: User can get trips searched by the preference. | ||
* description: user can search trips by originId, destinationId, | ||
* status, trip type,trip owner's first name | ||
* tags: | ||
* - Search | ||
* parameters: | ||
* - name: token | ||
* in: header | ||
* required: true | ||
* description: user token | ||
* schema: | ||
* $ref: '#/components/schemas/Search' | ||
* - name: searchPreference | ||
* in: path | ||
* required: true | ||
* description: search trips by originId, destinationId, | ||
* status, trip type,trip owner's first name | ||
* schema: | ||
* $ref: '#/components/schemas/Search' | ||
* - name: search query key | ||
* in: query | ||
* required: true | ||
* description: search trips by originId, destinationId, | ||
* status, trip type,trip owner's first name | ||
* schema: | ||
* type: string | ||
* responses: | ||
* 200: | ||
* description: Successfully retrieved trip requests by that search query parameter | ||
* 401: | ||
* description: Unauthorized | ||
* 400: | ||
* description: Bad Request | ||
* 403: | ||
* description: Forbidden | ||
* 500: | ||
* description: Internal server error | ||
*/ | ||
|
||
searchRoute | ||
.get( | ||
'/', | ||
verifyToken, | ||
isUserVerified, | ||
validateSearchQueries, | ||
searchedByPreference, | ||
); | ||
export default searchRoute; |
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,24 @@ | ||
import { trips } from '../database/models'; | ||
import CommonQueries from './CommonQueries'; | ||
import commonSearchQueries from './commonSearchQueries'; | ||
/** | ||
* @exports | ||
* @class searchService | ||
*/ | ||
class SearchService { | ||
/** | ||
* users can search for a trip by preference | ||
* @static | ||
* @description GET /api/search | ||
* @param {object} req request object | ||
* @memberof SearchService | ||
* @returns {object} data | ||
*/ | ||
static async searchByPreference(req) { | ||
const { query } = req; | ||
const searchingTrips = await CommonQueries.findAll(trips, commonSearchQueries(query)); | ||
return searchingTrips; | ||
} | ||
} | ||
|
||
export default SearchService; |
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,76 @@ | ||
import { Op } from 'sequelize'; | ||
import { | ||
tripRequests, status, tripTypes, users | ||
} from '../database/models'; | ||
|
||
|
||
const query = (searchQueryParams) => ({ | ||
where: { | ||
...searchQueryParams.startDate && { | ||
startDate: { | ||
[Op.iLike]: `%${searchQueryParams.startDate.trim()}%` | ||
} | ||
}, | ||
...searchQueryParams.returnDate && { | ||
returnDate: { | ||
[Op.iLike]: `%${searchQueryParams.returnDate.trim()}%` | ||
} | ||
}, | ||
...searchQueryParams.originId && { | ||
originId: { | ||
[Op.eq]: parseInt((searchQueryParams.originId), 10) | ||
} | ||
}, | ||
...searchQueryParams.destinationId && { | ||
destinationId: { | ||
[Op.eq]: parseInt((searchQueryParams.destinationId), 10) | ||
} | ||
}, | ||
}, | ||
attributes: ['originId', 'destinationId', 'reason', 'startDate', 'returnDate'], | ||
include: [ | ||
{ | ||
model: tripRequests, | ||
attributes: ['id'], | ||
include: [ | ||
{ | ||
model: status, | ||
where: { | ||
...searchQueryParams.status && { | ||
status: { | ||
[Op.iLike]: `%${searchQueryParams.status.trim()}%` | ||
} | ||
} | ||
}, | ||
attributes: ['status'], | ||
}, | ||
{ | ||
model: tripTypes, | ||
where: { | ||
...searchQueryParams.tripType && { | ||
tripType: { | ||
[Op.iLike]: `%${searchQueryParams.tripType.trim()}%` | ||
} | ||
} | ||
}, | ||
attributes: ['tripType'], | ||
}, | ||
{ | ||
model: users, | ||
where: { | ||
...searchQueryParams.firstName && { | ||
firstName: { | ||
[Op.iLike]: `%${searchQueryParams.firstName.trim()}%` | ||
} | ||
} | ||
}, | ||
attributes: ['firstName'], | ||
} | ||
] | ||
} | ||
], | ||
} | ||
); | ||
|
||
|
||
export default query; |
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,94 @@ | ||
import chai, { expect } from 'chai'; | ||
import chaiHttp from 'chai-http'; | ||
import dotenv from 'dotenv'; | ||
import app from '../index'; | ||
import mockData from './mock/mockData'; | ||
|
||
chai.use(chaiHttp); | ||
chai.should(); | ||
|
||
dotenv.config(); | ||
let userToken; | ||
|
||
describe('Search functionality tests', () => { | ||
before((done) => { | ||
chai.request(app) | ||
.post('/api/v1/auth/signin') | ||
.send(mockData.testUser) | ||
.end((err, res) => { | ||
userToken = res.body.data; | ||
|
||
done(err); | ||
}); | ||
}); | ||
|
||
before((done) => { | ||
chai.request(app) | ||
.get('/api/v1/search') | ||
.set('token', userToken) | ||
.send(mockData.usersSignin) | ||
.end(() => { | ||
done(); | ||
}); | ||
}); | ||
|
||
it('should not search anything if no search query key parameter has been provided', (done) => { | ||
chai.request(app) | ||
.get('/api/v1/search') | ||
.set('token', userToken) | ||
.end((err, res) => { | ||
expect(res.body.message).eql('Please provide a search query key parameter'); | ||
res.should.have.status(400); | ||
res.body.should.be.an('object'); | ||
done(err); | ||
}); | ||
}); | ||
|
||
it('should not search anything if the search query key parameter provided is invalid', (done) => { | ||
chai.request(app) | ||
.get('/api/v1/search?monkey') | ||
.set('token', userToken) | ||
.end((err, res) => { | ||
expect(res.body.message).eql('You provided an invalid search query key(s) parameter.Your search key should be either originId, destinationId, startDate, returnDate, firstName, status, or tripType'); | ||
res.should.have.status(400); | ||
res.body.should.be.an('object'); | ||
done(err); | ||
}); | ||
}); | ||
|
||
it('should not search anything if no search query value parameter has not been provided', (done) => { | ||
chai.request(app) | ||
.get('/api/v1/search?startDate=') | ||
.set('token', userToken) | ||
.end((err, res) => { | ||
expect(res.body.message).eql('Please provide a search value parameter'); | ||
res.should.have.status(400); | ||
res.body.should.be.an('object'); | ||
done(err); | ||
}); | ||
}); | ||
|
||
it('should return an error message if search for trips if the search query cannot be found has been provided', (done) => { | ||
chai.request(app) | ||
.get('/api/v1/search?firstName=Ivy') | ||
.set('token', userToken) | ||
.end((err, res) => { | ||
expect(res.body.message).eql('Oooops! No trips matching this search query parameter were found!'); | ||
res.should.have.status(404); | ||
res.body.should.be.an('object'); | ||
done(err); | ||
}); | ||
}); | ||
|
||
it('should return searched trips if search query information is valid', (done) => { | ||
chai.request(app) | ||
.get('/api/v1/search?status=rejected') | ||
.set('token', userToken) | ||
.end((err, res) => { | ||
expect(res.body.message).eql('Successfully retrieved trip requests by that search query parameter'); | ||
res.should.have.status(200); | ||
res.body.should.be.an('object'); | ||
done(err); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.