Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implement Pagination for GET /progreses API #2325

Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions controllers/progresses.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const {
getProgressDocument,
getRangeProgressData,
getProgressByDate,
getPaginatedProgressDocument,
} = require("../models/progresses");
const { PROGRESSES_RESPONSE_MESSAGES, INTERNAL_SERVER_ERROR_MESSAGE } = require("../constants/progresses");
const { sendTaskUpdate } = require("../utils/sendTaskUpdate");
Expand Down Expand Up @@ -107,7 +108,34 @@ const createProgress = async (req, res) => {
*/

const getProgress = async (req, res) => {
const { dev, page = 0, size = 100, type, userId, taskId } = req.query;
AnujChhikara marked this conversation as resolved.
Show resolved Hide resolved
try {
if (dev === "true") {
const { progressDocs, totalProgressCount } = await getPaginatedProgressDocument(req.query);
const limit = parseInt(size, 10);
const offset = parseInt(page, 10) * limit;
AnujChhikara marked this conversation as resolved.
Show resolved Hide resolved
const nextPage = offset + limit < totalProgressCount ? parseInt(page, 10) + 1 : null;
const prevPage = page > 0 ? parseInt(page, 10) - 1 : null;
let baseUrl = `${req.baseUrl}`;
AnujChhikara marked this conversation as resolved.
Show resolved Hide resolved
if (type) {
baseUrl += `?type=${type}`;
} else if (userId) {
baseUrl += `?userId=${userId}`;
} else if (taskId) {
baseUrl += `?taskId=${taskId}`;
}
const nextLink = nextPage !== null ? `${baseUrl}&page=${nextPage}&size=${size}&dev=${dev}` : null;
const prevLink = prevPage !== null ? `${baseUrl}&page=${prevPage}&size=${size}&dev=${dev}` : null;
AnujChhikara marked this conversation as resolved.
Show resolved Hide resolved
return res.json({
message: PROGRESS_DOCUMENT_RETRIEVAL_SUCCEEDED,
count: progressDocs.length,
data: progressDocs,
links: {
prev: prevLink,
next: nextLink,
},
});
}
const data = await getProgressDocument(req.query);
return res.json({
message: PROGRESS_DOCUMENT_RETRIEVAL_SUCCEEDED,
Expand Down
12 changes: 12 additions & 0 deletions middlewares/validators/progresses.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,18 @@ const validateGetProgressRecordsQuery = async (req, res, next) => {
.messages({
"string.base": "orderBy must be a string",
}),
dev: joi.boolean().optional().messages({
"any.only": "dev field is restricted to either 'true' or 'false'",
}),
size: joi.number().optional().min(1).max(100).messages({
"number.base": "size must be a number",
"number.min": "size must be in the range 1-100",
"number.max": "size must be in the range 1-100",
}),
page: joi.number().optional().min(0).messages({
"number.base": "page must be a number",
"number.min": "page must be a positive number or zero",
}),
})
.xor("type", "userId", "taskId")
.messages({
Expand Down
27 changes: 26 additions & 1 deletion models/progresses.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
assertTaskExists,
getProgressDateTimestamp,
buildQueryToSearchProgressByDay,
buildQueryToFetchPaginatedDocs,
getPaginatedProgressDocs,
} = require("../utils/progresses");
const { PROGRESS_ALREADY_CREATED, PROGRESS_DOCUMENT_NOT_FOUND } = PROGRESSES_RESPONSE_MESSAGES;

Expand Down Expand Up @@ -53,6 +55,23 @@
return progressDocs;
};

/**
* This function retrieves a paginated list of progress documents for a specific user or task,
* or for all users or all tasks if no specific user or task is provided.
* @param queryParams {object} This is the data that will be used for querying.
* It should be an object that includes key-value pairs for the fields - type, userId, taskId,
* and optionally pagination details such as page number and page size.
* @returns {Promise<object>} A Promise that resolves with the paginated progress document objects.
* @throws {Error} If the userId or taskId is invalid or does not exist.
**/
AnujChhikara marked this conversation as resolved.
Show resolved Hide resolved
const getPaginatedProgressDocument = async (queryParams) => {
await assertUserOrTaskExists(queryParams);
const page = queryParams.page || 0;
const { baseQuery, totalProgressCount } = await buildQueryToFetchPaginatedDocs(queryParams);

const progressDocs = await getPaginatedProgressDocs(baseQuery, page);
return { progressDocs, totalProgressCount };
};
AnujChhikara marked this conversation as resolved.
Show resolved Hide resolved
/**
* This function fetches the progress records for a particular user or task within the specified date range, from start to end date.
* @param queryParams {object} This is the data that will be used for querying. It should be an object that includes key-value pairs for the fields - userId, taskId, startDate, and endDate.
Expand All @@ -79,8 +98,8 @@
**/
async function getProgressByDate(pathParams) {
const { type, typeId, date } = pathParams;
await assertUserOrTaskExists({ [TYPE_MAP[type]]: typeId });

Check warning on line 101 in models/progresses.js

View workflow job for this annotation

GitHub Actions / build (20.11.x)

Generic Object Injection Sink
const query = buildQueryToSearchProgressByDay({ [TYPE_MAP[type]]: typeId, date });

Check warning on line 102 in models/progresses.js

View workflow job for this annotation

GitHub Actions / build (20.11.x)

Generic Object Injection Sink
const result = await query.get();
if (!result.size) {
throw new NotFound(PROGRESS_DOCUMENT_NOT_FOUND);
Expand All @@ -89,4 +108,10 @@
return { id: doc.id, ...doc.data() };
}

module.exports = { createProgressDocument, getProgressDocument, getRangeProgressData, getProgressByDate };
module.exports = {
createProgressDocument,
getProgressDocument,
getPaginatedProgressDocument,
getRangeProgressData,
getProgressByDate,
};
69 changes: 69 additions & 0 deletions utils/progresses.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,48 @@ const buildQueryToFetchDocs = (queryParams) => {
}
};

/**
* Builds a Firestore query for retrieving a paginated list of progress documents within a date range
* and optionally filtered by user ID or task ID.
* @param {Object} queryParams - An object containing the query parameters.
* @param {string} queryParams.userId - (Optional) The user ID to filter progress documents by.
* @param {string} queryParams.taskId - (Optional) The task ID to filter progress documents by.
* @param {string} queryParams.type - (Optional) The type to filter progress documents by.
* @param {string} queryParams.orderBy - (Optional) The field to sort the documents by.
* @param {number} queryParams.size - (Optional) The number of documents per page. Defaults to 100.
* @param {number} queryParams.page - (Optional) The page number for pagination. Defaults to 0 (first page).
* @returns {Query} A Firestore query object that filters and paginates progress documents based on the given parameters.
*/
AnujChhikara marked this conversation as resolved.
Show resolved Hide resolved

const buildQueryToFetchPaginatedDocs = async (queryParams) => {
const { type, userId, taskId, orderBy, size = 100, page = 0 } = queryParams;
const orderByField = PROGRESS_VALID_SORT_FIELDS[0];
const isAscOrDsc = orderBy && PROGRESS_VALID_SORT_FIELDS[0] === orderBy ? "asc" : "desc";
const limit = parseInt(size, 10);
const offset = parseInt(page, 10) * limit;

let baseQuery;
if (type) {
baseQuery = progressesCollection.where("type", "==", type).orderBy(orderByField, isAscOrDsc);
} else if (userId) {
baseQuery = progressesCollection
.where("type", "==", "user")
.where("userId", "==", userId)
.orderBy(orderByField, isAscOrDsc);
} else {
baseQuery = progressesCollection
.where("type", "==", "task")
.where("taskId", "==", taskId)
.orderBy(orderByField, isAscOrDsc);
}

const totalProgress = await baseQuery.get();
const totalProgressCount = totalProgress.size;

baseQuery = baseQuery.limit(limit).offset(offset);
return { baseQuery, totalProgressCount };
AnujChhikara marked this conversation as resolved.
Show resolved Hide resolved
};

/**
* Retrieves progress documents from Firestore based on the given query.
* @param {Query} query - A Firestore query object for fetching progress documents.
Expand All @@ -137,6 +179,31 @@ const getProgressDocs = async (query) => {
});
return docsData;
};
/**
* Retrieves progress documents from Firestore based on the given query and page number.
*
* @param {Query} query - A Firestore query object for fetching progress documents.
* @param {number} [pageNumber] - The current page number (optional). If not provided, it will check for documents without pagination.
* @returns {Array.<Object>} An array of objects representing the retrieved progress documents.
* Each object contains the document ID (`id`) and its associated data.
*
* @throws {NotFound} If no progress documents are found and no page number is specified.
*/
const getPaginatedProgressDocs = async (query, page) => {
const progressesDocs = await query.get();
if (!page && !progressesDocs.size) {
throw new NotFound(PROGRESS_DOCUMENT_NOT_FOUND);
}
if (!progressesDocs.size) {
return [];
}
const docsData = [];
progressesDocs.forEach((doc) => {
docsData.push({ id: doc.id, ...doc.data() });
});
return docsData;
};

/**
* Builds a Firestore query for retrieving progress documents within a date range and optionally filtered by user ID or task ID.
* @param {Object} queryParams - An object containing the query parameters.
Expand Down Expand Up @@ -231,8 +298,10 @@ module.exports = {
assertUserOrTaskExists,
buildQueryToFetchDocs,
getProgressDocs,
getPaginatedProgressDocs,
buildRangeProgressQuery,
getProgressRecords,
buildQueryToSearchProgressByDay,
buildProgressQueryForMissedUpdates,
buildQueryToFetchPaginatedDocs,
};
Loading