Skip to content

Commit c32124e

Browse files
authored
feat: added feature to get paginated requests (#1908)
1 parent 5451808 commit c32124e

File tree

6 files changed

+189
-44
lines changed

6 files changed

+189
-44
lines changed

controllers/requests.ts

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
} from "../constants/requests";
1313
import { createRequest, getRequests, updateRequest } from "../models/requests";
1414
import { addLog } from "../models/logs";
15+
import { getPaginatedLink } from "../utils/helper";
1516

1617
export const createRequestController = async (req: any, res: any) => {
1718
const requestBody = req.body;
@@ -113,9 +114,47 @@ export const getRequestsController = async (req: any, res: any) => {
113114
if (!requests) {
114115
return res.status(204).send();
115116
}
116-
return res.status(200).send({
117+
118+
const { allRequests, next, prev, page } = requests;
119+
if (allRequests.length === 0) {
120+
return res.status(204).send();
121+
}
122+
123+
if(page) {
124+
const pageLink = `/requests?page=${page}&dev=${query.dev}`;
125+
return res.status(200).json({
126+
message: REQUEST_FETCHED_SUCCESSFULLY,
127+
data: allRequests,
128+
page: pageLink,
129+
});
130+
}
131+
132+
let nextUrl = null;
133+
let prevUrl = null;
134+
if (next) {
135+
const nextLink = getPaginatedLink({
136+
endpoint: "/requests",
137+
query,
138+
cursorKey: "next",
139+
docId: next,
140+
});
141+
nextUrl = nextLink;
142+
}
143+
if (prev) {
144+
const prevLink = getPaginatedLink({
145+
endpoint: "/requests",
146+
query,
147+
cursorKey: "prev",
148+
docId: prev,
149+
});
150+
prevUrl = prevLink;
151+
}
152+
153+
return res.status(200).json({
117154
message: REQUEST_FETCHED_SUCCESSFULLY,
118-
data: requests,
155+
data: allRequests,
156+
next: nextUrl,
157+
prev: prevUrl,
119158
});
120159
} catch (err) {
121160
logger.error(ERROR_WHILE_FETCHING_REQUEST, err);

middlewares/validators/requests.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,17 @@ export const getRequestsMiddleware = async (req: OooRequestCreateRequest, res: O
7474
.string()
7575
.valid(REQUEST_STATE.APPROVED, REQUEST_STATE.PENDING, REQUEST_STATE.REJECTED)
7676
.optional(),
77-
page: joi.number().integer().min(0),
77+
page: joi.number().integer().min(0).when("next", {
78+
is: joi.exist(),
79+
then: joi.forbidden().messages({
80+
"any.only": "next is not allowed when using page",
81+
}),
82+
}).when("prev", {
83+
is: joi.exist(),
84+
then: joi.forbidden().messages({
85+
"any.only": "page is not allowed when using prev",
86+
}),
87+
}).optional(),
7888
next: joi
7989
.string()
8090
.optional(),

models/requests.ts

Lines changed: 70 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import {
1010
REQUEST_ALREADY_PENDING,
1111
} from "../constants/requests";
1212
import * as admin from "firebase-admin";
13+
import { getUserId } from "../utils/users";
14+
const SIZE = 5;
1315

1416
export const createRequest = async (body: any) => {
1517
try {
@@ -76,41 +78,90 @@ export const updateRequest = async (id: string, body: any, lastModifiedBy: strin
7678
}
7779
};
7880

79-
export const getRequests = async (query: RequestQuery) => {
80-
const { id, type, requestedBy, state } = query;
81+
export const getRequests = async (query: any) => {
82+
let { id, type, requestedBy, state, prev, next, page, size = SIZE } = query;
83+
size = parseInt(size);
84+
page = parseInt(page);
8185
try {
82-
let requestQuery: admin.firestore.Query = requestModel;
86+
let requestQuery: any = requestModel;
87+
8388
if (id) {
84-
const requestsDoc = await requestModel.doc(id).get();
85-
const request = requestsDoc.data();
86-
if (!request) {
89+
const requestDoc = await requestModel.doc(id).get();
90+
if (!requestDoc.exists) {
8791
return null;
8892
}
8993
return {
90-
id,
91-
...request,
94+
allRequests: [
95+
{
96+
id: requestDoc.id,
97+
...requestDoc.data(),
98+
},
99+
],
92100
};
93101
}
102+
103+
if (requestedBy) {
104+
const requestedByUserId = await getUserId(requestedBy);
105+
requestQuery = requestQuery.where("requestedBy", "==", requestedByUserId);
106+
}
94107
if (type) {
95108
requestQuery = requestQuery.where("type", "==", type);
96109
}
97-
if (requestedBy) {
98-
requestQuery = requestQuery.where("requestedBy", "==", requestedBy);
99-
}
100110
if (state) {
101111
requestQuery = requestQuery.where("state", "==", state);
102112
}
103113

104-
const requestsDoc = await requestQuery.get();
105-
if (requestsDoc.empty) {
114+
requestQuery = requestQuery.orderBy("createdAt", "desc");
115+
116+
let requestQueryDoc = requestQuery;
117+
118+
if (prev) {
119+
requestQueryDoc = requestQueryDoc.limitToLast(size);
120+
} else {
121+
requestQueryDoc = requestQueryDoc.limit(size);
122+
}
123+
124+
if (page) {
125+
const startAfter = (page - 1) * size;
126+
requestQueryDoc = requestQueryDoc.offset(startAfter);
127+
} else if (next) {
128+
const doc = await requestModel.doc(next).get();
129+
requestQueryDoc = requestQueryDoc.startAt(doc);
130+
} else if (prev) {
131+
const doc = await requestModel.doc(prev).get();
132+
requestQueryDoc = requestQueryDoc.endAt(doc);
133+
}
134+
135+
const snapshot = await requestQueryDoc.get();
136+
let nextDoc: any, prevDoc: any;
137+
138+
if (!snapshot.empty) {
139+
const first = snapshot.docs[0];
140+
prevDoc = await requestQuery.endBefore(first).limitToLast(1).get();
141+
142+
const last = snapshot.docs[snapshot.docs.length - 1];
143+
nextDoc = await requestQuery.startAfter(last).limit(1).get();
144+
}
145+
146+
let allRequests = [];
147+
if (!snapshot.empty) {
148+
snapshot.forEach((doc: any) => {
149+
allRequests.push({
150+
id: doc.id,
151+
...doc.data(),
152+
});
153+
});
154+
}
155+
if (allRequests.length === 0) {
106156
return null;
107157
}
108-
return requestsDoc.docs.map((doc: any) => {
109-
return {
110-
id: doc.id,
111-
...doc.data(),
112-
};
113-
});
158+
159+
return {
160+
allRequests,
161+
prev: prevDoc.empty ? null : prevDoc.docs[0].id,
162+
next: nextDoc.empty ? null : nextDoc.docs[0].id,
163+
page: page ? page + 1 : null,
164+
};
114165
} catch (error) {
115166
logger.error(ERROR_WHILE_FETCHING_REQUEST, error);
116167
throw error;

test/integration/requests.test.ts

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,17 +34,23 @@ let superUserToken: string;
3434
let oooRequestId: string;
3535
let pendingOooRequestId: string;
3636
let approvedOooRequestId: string;
37+
let oooRequestData: any;
38+
let oooRequestData2: any;
39+
let testUserId: string;
3740

3841
describe("/requests", function () {
3942
beforeEach(async function () {
40-
const { id: oooRequestStatusId }: any = await createRequest(createOooRequests);
41-
oooRequestId = oooRequestStatusId;
42-
43-
const { id: pendingOooId }: any = await createRequest(createOooRequests2);
44-
pendingOooRequestId = pendingOooId;
45-
4643
const userIdPromises = [addUser(userData[16]), addUser(userData[4])];
4744
const [userId, superUserId] = await Promise.all(userIdPromises);
45+
testUserId = userId;
46+
47+
oooRequestData = { ...createOooRequests, requestedBy: userId };
48+
oooRequestData2 = { ...createOooRequests2, requestedBy: superUserId };
49+
50+
const { id: oooRequestStatusId }: any = await createRequest(oooRequestData);
51+
oooRequestId = oooRequestStatusId;
52+
const { id: pendingOooId }: any = await createRequest(oooRequestData2);
53+
pendingOooRequestId = pendingOooId;
4854

4955
const { id: approveOooId }: any = await updateRequest(oooRequestId, { state: "APPROVED" }, superUserId);
5056
approvedOooRequestId = approveOooId;
@@ -294,21 +300,23 @@ describe("/requests", function () {
294300
it("should return all requests by specific user", function (done) {
295301
chai
296302
.request(app)
297-
.get("/requests?dev=true&requestedBy=testUser")
303+
.get(`/requests?dev=true&requestedBy=${userData[16].username}`)
298304
.end(function (err, res) {
305+
console.log(res.body.data);
299306
expect(res).to.have.status(200);
300-
expect(res.body.data.every((request: any) => request.requestedBy === "testUser"));
307+
expect(res.body.data.every((request: any) => request.requestedBy === testUserId));
301308
done();
302309
});
303310
});
304311

305312
it("should return all requests by specific user and state", function (done) {
306313
chai
307314
.request(app)
308-
.get("/requests?dev=true&requestedBy=testUser&state=APPROVED")
315+
.get(`/requests?dev=true&state=APPROVED&requestedBy=${userData[16].username}`)
309316
.end(function (err, res) {
310317
expect(res).to.have.status(200);
311-
expect(res.body.data.every((e: any) => e.requestedBy === "testUser" && e.state === "APPROVED"));
318+
expect(res.body.data.every((e: any) => e.state === "APPROVED"));
319+
expect(res.body.data.every((e: any) => e.requestedBy === testUserId));
312320
done();
313321
});
314322
});

test/unit/models/requests.test.ts

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,18 @@ import {
99
updateOooRejectedRequests,
1010
} from "./../../fixtures/oooRequest/oooRequest";
1111
import { REQUEST_ALREADY_PENDING, REQUEST_STATE, REQUEST_TYPE } from "../../../constants/requests";
12+
import userDataFixture from "./../../fixtures/user/user";
13+
import addUser from "../../utils/addUser";
14+
const userData = userDataFixture();
1215

16+
let testUserId: string;
1317
describe("models/oooRequests", () => {
18+
beforeEach(async () => {
19+
const userIdPromises = [addUser(userData[16])];
20+
const [userId] = await Promise.all(userIdPromises);
21+
testUserId = userId;
22+
});
23+
1424
afterEach(async () => {
1525
await cleanDb();
1626
});
@@ -90,54 +100,77 @@ describe("models/oooRequests", () => {
90100
it("Should return the request with the specified ID", async () => {
91101
const oooRequest = await createRequest(createOooRequests2);
92102
const query = { id: oooRequest.id, dev: "true" };
93-
const oooRequestData:any = await getRequests(query);
94-
expect(oooRequestData).to.have.property("id");
95-
expect(oooRequestData.id).to.be.equal(oooRequest.id);
103+
const oooRequestData: any = await getRequests(query);
104+
expect(oooRequestData).to.have.property("allRequests");
105+
expect(oooRequestData.allRequests[0].id).to.be.equal(oooRequest.id);
106+
expect(oooRequestData.allRequests).to.have.lengthOf(1);
107+
});
108+
109+
it("Should return null if the request with the specified ID does not exist", async () => {
110+
const query = { id: "randomId", dev: "true" };
111+
const oooRequestData = await getRequests(query);
112+
expect(oooRequestData).to.be.equal(null);
96113
});
97114

98115
it("Should return a list of all the GET requests", async () => {
99116
await createRequest(createOooRequests);
100117
await createRequest(createOooRequests2);
101118
const query = { dev: "true" };
102119
const oooRequestData = await getRequests(query);
103-
expect(oooRequestData).to.be.have.length(2);
120+
expect(oooRequestData.allRequests).to.be.have.length(2);
104121
});
105122

106123
it("Should return a list of all the requests with specified state - APPROVED", async () => {
107124
const oooRequest: any = await createRequest(createOooStatusRequests);
108125
await updateRequest(oooRequest.id, updateOooApprovedRequests, updateOooApprovedRequests.lastModifiedBy);
109126
const query = { dev: "true", state: REQUEST_STATE.APPROVED };
110127
const oooRequestData = await getRequests(query);
111-
expect(oooRequestData[0].state).to.be.equal(REQUEST_STATE.APPROVED);
128+
expect(oooRequestData.allRequests[0].state).to.be.equal(REQUEST_STATE.APPROVED);
112129
});
113130

114131
it("Should return a list of all the requests with specified state - PENDING", async () => {
115132
await createRequest(createOooStatusRequests);
116133
const query = { dev: "true", state: REQUEST_STATE.PENDING };
117134
const oooRequestData = await getRequests(query);
118-
expect(oooRequestData[0].state).to.be.equal(REQUEST_STATE.PENDING);
135+
expect(oooRequestData.allRequests[0].state).to.be.equal(REQUEST_STATE.PENDING);
119136
});
120137

121138
it("Should return a list of all the requests by specific user ", async () => {
122-
const oooRequest = await createRequest(createOooRequests);
123-
const query = { dev: "true", requestedBy: oooRequest.requestedBy };
139+
const oooRequestBodyData = { ...createOooRequests, requestedBy: testUserId };
140+
await createRequest(oooRequestBodyData);
141+
const query = { dev: "true", requestedBy: userData[16].username };
124142
const oooRequestData = await getRequests(query);
125-
expect(oooRequestData).to.have.lengthOf(1);
126-
expect(oooRequestData[0].requestedBy).to.be.equal(oooRequest.requestedBy);
143+
expect(oooRequestData.allRequests).to.have.lengthOf(1);
144+
expect(oooRequestData.allRequests[0].requestedBy).to.be.equal(testUserId);
127145
});
128146

129147
it("Should return a list of all the requests for specific type ", async () => {
130148
await createRequest(createOooRequests);
131149
const query = { dev: "true", type: REQUEST_TYPE.OOO };
132150
const oooRequestData = await getRequests(query);
133-
expect(oooRequestData).to.have.lengthOf(1);
134-
expect(oooRequestData[0].type).to.be.equal(REQUEST_TYPE.OOO);
151+
expect(oooRequestData.allRequests[0].type).to.be.equal(REQUEST_TYPE.OOO);
135152
});
136153

137154
it("Should return empty array if no data is found", async () => {
138155
const query = { dev: "true", state: REQUEST_STATE.PENDING };
139156
const oooRequestData = await getRequests(query);
140157
expect(oooRequestData).to.be.equal(null);
141158
});
159+
160+
it("Should return a list of all the requests by page ", async () => {
161+
await createRequest(createOooRequests);
162+
await createRequest(createOooRequests2);
163+
const query = { dev: "true", page: 1 };
164+
const oooRequestData = await getRequests(query);
165+
expect(oooRequestData.page).to.be.equal(2);
166+
});
167+
168+
it("Should return a list of all the requests by size ", async () => {
169+
await createRequest(createOooRequests);
170+
await createRequest(createOooRequests2);
171+
const query = { dev: "true", size: 1 };
172+
const oooRequestData = await getRequests(query);
173+
expect(oooRequestData.allRequests).to.have.lengthOf(1);
174+
});
142175
});
143176
});

types/oooRequest.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ export type RequestQuery = {
4545
requestedBy?: string;
4646
state?: REQUEST_STATE.APPROVED | REQUEST_STATE.PENDING | REQUEST_STATE.REJECTED;
4747
id?: string;
48+
prev?: string;
49+
next?: string;
50+
page?: number;
51+
size?: number;
4852
};
4953

5054
export type RequestParams = {

0 commit comments

Comments
 (0)