diff --git a/__test__/api/applications/getSingleApplicationLog.test.js b/__test__/api/applications/getSingleApplicationLog.test.js index f09fd51..a34fa73 100644 --- a/__test__/api/applications/getSingleApplicationLog.test.js +++ b/__test__/api/applications/getSingleApplicationLog.test.js @@ -9,7 +9,7 @@ describe("Get single application logs", () => { let app, request; it("should return all logs for a given application", async () => { - process.env.LOGGING_ENABLED = true; + process.env.loggingEnabled = true; //create express app app = express(); diff --git a/__test__/api/msadmins/getAllMsAdmins.test.js b/__test__/api/msadmins/getAllMsAdmins.test.js index cfcbd70..2ddb95d 100644 --- a/__test__/api/msadmins/getAllMsAdmins.test.js +++ b/__test__/api/msadmins/getAllMsAdmins.test.js @@ -2,6 +2,7 @@ const app = require("../../../server"); const supertest = require("supertest"); const MsAdmin = require("../../../models/msadmins"); const request = supertest(app); +const { deleteById, restoreById } = require("../../../utils/softDelete"); // Cached admin and allMsAdminsResponse. let msAdmin, msAdmin2, msAdmin3, allMsAdminsResponse; @@ -44,18 +45,21 @@ describe("GET /msAdmins", () => { fullname: msAdmin.fullname, email: msAdmin.email, role: msAdmin.role, + isDisabled: false, }, { msAdminId: msAdmin2.id, fullname: msAdmin2.fullname, email: msAdmin2.email, role: msAdmin2.role, + isDisabled: false, }, { msAdminId: msAdmin3.id, fullname: msAdmin3.fullname, email: msAdmin3.email, role: msAdmin3.role, + isDisabled: false, }, ]; }); @@ -179,4 +183,46 @@ describe("GET /msAdmins", () => { expect(res.body.status).toEqual("error"); expect(res.body.data).toEqual([]); }); + + it("Should get all disabled microservice admins with all pagination params set", async () => { + const url = `/v1/msAdmins`; + const bearerToken = `bearer ${global.superSysToken}`; + + //disable one admin + await deleteById(MsAdmin, msAdmin2.id, global.msSuperAdmin.id); + + const res = await request + .get(url) + .query({ limit: 3, page: 1, sort: "asc", filter: "disabled" }) + .set("Authorization", bearerToken); + + // const expectedValue = allMsAdminsResponse.slice(0, 1); + + expect(res.status).toEqual(200); + expect(res.body.status).toEqual("success"); + expect(res.body.data.records.length).toEqual(1); + + await restoreById(MsAdmin, msAdmin2.Id); + }); + + it("Should get both disabled/enabled microservice admins with all pagination params set", async () => { + const url = `/v1/msAdmins`; + const bearerToken = `bearer ${global.superSysToken}`; + + //disable one admin + await deleteById(MsAdmin, msAdmin2.id, global.msSuperAdmin.id); + + const res = await request + .get(url) + .query({ limit: 3, page: 1, sort: "asc", filter: "all" }) + .set("Authorization", bearerToken); + + // const expectedValue = allMsAdminsResponse.slice(0, 1); + + expect(res.status).toEqual(200); + expect(res.body.status).toEqual("success"); + expect(res.body.data.records.length).toEqual(3); + + await restoreById(MsAdmin, msAdmin2.Id); + }); }); diff --git a/__test__/api/msadmins/settings/updateSystemSettings.test.js b/__test__/api/msadmins/settings/updateSystemSettings.test.js index edefc85..f6b763a 100644 --- a/__test__/api/msadmins/settings/updateSystemSettings.test.js +++ b/__test__/api/msadmins/settings/updateSystemSettings.test.js @@ -21,6 +21,8 @@ describe("PATCH /msAdmins/settings", () => { maxItemsPerPage: 50, defaultMaxRequestsPerDay: 10000, disableRequestLimits: false, + loggingEnabled: true, + logPageSize: 100, }; let res = await request @@ -46,6 +48,8 @@ describe("PATCH /msAdmins/settings", () => { maxItemsPerPage: 20, defaultMaxRequestsPerDay: 10000, disableRequestLimits: false, + loggingEnabled: true, + logPageSize: 100, }; //check for update also in same process because this is a single document collection diff --git a/__test__/middleware/requestLogger.test.js b/__test__/middleware/requestLogger.test.js index 5bd5f4e..b115aa9 100644 --- a/__test__/middleware/requestLogger.test.js +++ b/__test__/middleware/requestLogger.test.js @@ -43,7 +43,7 @@ describe("Logger middleware", () => { let app, request; it("logging is system-enabled and app-enabled for all requests", async () => { - process.env.LOGGING_ENABLED = true; + process.env.loggingEnabled = true; app = express(); app.use((req, res, next) => { const logging = { @@ -79,7 +79,7 @@ describe("Logger middleware", () => { }); it("logging is system-disabled", async () => { - process.env.LOGGING_ENABLED = false; + process.env.loggingEnabled = false; app = express(); app.use((req, res, next) => { const logging = { @@ -115,7 +115,7 @@ describe("Logger middleware", () => { }); it("logging is not available for application", async () => { - process.env.LOGGING_ENABLED = true; + process.env.loggingEnabled = true; app = express(); app.use((req, res, next) => { const logging = {}; @@ -151,7 +151,7 @@ describe("Logger middleware", () => { }); it("logging is system-enabled and app-enabled for all requests but skipped status 400", async () => { - process.env.LOGGING_ENABLED = true; + process.env.loggingEnabled = true; app = express(); app.use((req, res, next) => { const logging = { @@ -207,7 +207,7 @@ describe("Logger middleware", () => { }); it("logging is system-disabled when env variable is undefined", async () => { - process.env.LOGGING_ENABLED = ""; + process.env.loggingEnabled = ""; app = express(); app.use((req, res, next) => { const logging = { diff --git a/__test__/utils/paginateDeleted.test.js b/__test__/utils/paginateDeleted.test.js new file mode 100644 index 0000000..a4df5ac --- /dev/null +++ b/__test__/utils/paginateDeleted.test.js @@ -0,0 +1,69 @@ +const { paginateDeleted } = require("../../utils/paginateDeleted"); +const softDelete = require("../../utils/softDelete"); +const MsAdmin = require("../../models/msadmins"); + +describe("Paginete soft-deleted records", () => { + let msAdmin, + idsArray = []; + beforeAll(async () => { + //create dummy records and softDelete them + for (let index = 0; index < 20; index++) { + msAdmin = new MsAdmin({ + fullname: "paginateDeleteTest", + email: `admin${Date.now()}@domain.com`, + password: "some password", + }); + await msAdmin.save(); + + idsArray.push(msAdmin.id); + + //soft delete + if ((index + 2) % 2 === 0) { + await softDelete.deleteById(MsAdmin, msAdmin.id, global.msAdmin.id); + } + } + }); + + afterAll(async () => { + for (let index = 0; index < idsArray.length; index++) { + const msAdminId = idsArray[index]; + await softDelete.restoreById(MsAdmin, msAdminId); + await MsAdmin.findByIdAndDelete(msAdminId); + } + }); + + it("should get all deleted records", async () => { + const deletedAdmins = await paginateDeleted( + MsAdmin, + "disabled", + { fullname: "paginateDeleteTest" }, + 5, + 1 + ); + expect(deletedAdmins.totalRecords).toEqual(10); + }); + + it("should get all undeleted records", async () => { + const unDeleted = await paginateDeleted( + MsAdmin, + "enabled", + { fullname: "paginateDeleteTest" }, + 5, + 1 + ); + expect(unDeleted.totalRecords).toEqual(10); + }); + + it("should get all records", async () => { + const allAdmins = await paginateDeleted( + MsAdmin, + "all", + { fullname: "paginateDeleteTest" }, + 5, + 1 + ); + expect(allAdmins.totalRecords).toEqual(20); + }); + + //retrieve the deleted records by page +}); diff --git a/controllers/applicationsController/getSingleApplicationLog.js b/controllers/applicationsController/getSingleApplicationLog.js index 9f9dade..ce36368 100644 --- a/controllers/applicationsController/getSingleApplicationLog.js +++ b/controllers/applicationsController/getSingleApplicationLog.js @@ -11,13 +11,13 @@ const getSingleApplicationLog = async (req, res, next) => { //reuse this controller for msAdmins, they can find any application if (req.token.msAdminId) { - const application = Application.findById(applicationId); + const application = await Application.findById(applicationId); if (!application) { return next(new CustomError(404, "Application not found")); } } else { //check if requested application belongs to organization of admin user - const application = Application.find({ + const application = await Application.find({ _id: applicationId, organizationId, }); diff --git a/controllers/msAdminsController/applications/getAllApplications.js b/controllers/msAdminsController/applications/getAllApplications.js index 064a4cf..fc0b647 100644 --- a/controllers/msAdminsController/applications/getAllApplications.js +++ b/controllers/msAdminsController/applications/getAllApplications.js @@ -2,6 +2,10 @@ const Applications = require("../../../models/applications"); const MsAdmin = require("../../../models/msadmins"); const CustomError = require("../../../utils/customError"); const responseHandler = require("../../../utils/responseHandler"); +const { + getAllRecords, + getDeletedRecords, +} = require("../../../utils/softDelete"); /** * @author Ekeyekwu Oscar @@ -20,6 +24,7 @@ const getAllApplications = async (req, res, next) => { let allApplications; let query = {}; + const { page, limit } = req.paginateOptions; try { //check if msAdmin exists @@ -29,11 +34,21 @@ const getAllApplications = async (req, res, next) => { return; } + const { filter } = req.query; + //get all applications - const applications = await Applications.paginate(query, { - ...req.paginateOptions, - populate: "organizationId", - }); + let applications; + + if (filter === "disabled") { + applications = await getDeletedRecords(Applications, page, limit); + } else if (filter === "all") { + applications = await getAllRecords(Applications, page, limit); + } else { + applications = await Applications.paginate(query, { + ...req.paginateOptions, + populate: "organizationId", + }); + } allApplications = applications.docs.map((application) => { return { @@ -41,6 +56,7 @@ const getAllApplications = async (req, res, next) => { applicationName: application.name, organizationId: application.organizationId, organizationName: application.organizationId.name, + isBlocked: application.deleted || false, }; }); diff --git a/controllers/msAdminsController/applications/getSingleApplication.js b/controllers/msAdminsController/applications/getSingleApplication.js index bcf9c3e..8958057 100644 --- a/controllers/msAdminsController/applications/getSingleApplication.js +++ b/controllers/msAdminsController/applications/getSingleApplication.js @@ -29,35 +29,21 @@ const getSingleApplication = async (req, res, next) => { } //get applications - const singleApplication = await Applications.findById( - applicationId - ).populate("organizationId"); + const singleApplication = await Applications.findOneWithDeleted({ + _id: applicationId, + }).populate("organizationId"); if (!singleApplication) { next(new CustomError(404, "Application not Found")); return; } - // let commentsCount, repliesCount; - // const comments = await CommentModel.find({applicationId:applicationId}); - // console.log(comments) - // if (!comments || comments === []) { - // commentsCount = 0; - // repliesCount = 0; - // } else { - // commentsCount = comments.records.length; - // repliesCount = comments.records.reduce((acc, curr) => { - // return acc + curr.numOfReplies; - // }, 0); - // } - // console.log(commentsCount, repliesCount); application = { applicationId: singleApplication._id, applicationName: singleApplication.name, organizationId: singleApplication.organizationId, organizationName: singleApplication.organizationId.name, - // totalNumOfComments: commentsCount, - // totalNumOfReplies: repliesCount, + isBlocked: singleApplication.deleted || false, }; } catch (error) { next(new CustomError(400, "An error occured retrieving Application")); diff --git a/controllers/msAdminsController/getAllMsAdmins.js b/controllers/msAdminsController/getAllMsAdmins.js index 24a3575..927cc5b 100644 --- a/controllers/msAdminsController/getAllMsAdmins.js +++ b/controllers/msAdminsController/getAllMsAdmins.js @@ -1,6 +1,7 @@ const MsAdmin = require("../../models/msadmins"); const CustomError = require("../../utils/customError"); const responseHandler = require("../../utils/responseHandler"); +const { getAllRecords, getDeletedRecords } = require("../../utils/softDelete"); /** * @author David Okanlawon @@ -14,20 +15,31 @@ const responseHandler = require("../../utils/responseHandler"); const getAllMsAdmins = async (req, res, next) => { //get all admins mapping the field names appropriately let admins, allMsAdmins; + const { filter } = req.query; let query = {}; + const { page, limit } = req.paginateOptions; try { - admins = await MsAdmin.paginate(query, req.paginateOptions); + if (filter === "disabled") { + admins = await getDeletedRecords(MsAdmin, page, limit); + } else if (filter === "all") { + admins = await getAllRecords(MsAdmin, page, limit); + } else { + admins = await MsAdmin.paginate(query, req.paginateOptions); + } + allMsAdmins = admins.docs.map((admin) => { return { msAdminId: admin._id, fullname: admin.fullname, email: admin.email, role: admin.role, + isDisabled: admin.deleted || false, }; }); } catch (error) { + console.log(error.message); next(new CustomError(400, "An error occured retrieving admin accounts")); return; } diff --git a/controllers/msAdminsController/getSingleMsAdmin.js b/controllers/msAdminsController/getSingleMsAdmin.js index bbb3011..b4d7797 100644 --- a/controllers/msAdminsController/getSingleMsAdmin.js +++ b/controllers/msAdminsController/getSingleMsAdmin.js @@ -16,7 +16,7 @@ const getSingleMsAdmin = async (req, res, next) => { //get msAdmin account try { - const msAdmin = await MsAdmin.findById(msAdminId); + const msAdmin = await MsAdmin.findOneWithDeleted({ _id: msAdminId }); if (!msAdmin) { next(new CustomError(404, "MsAdmin account not found")); return; @@ -25,6 +25,7 @@ const getSingleMsAdmin = async (req, res, next) => { fullname: msAdmin.fullname, email: msAdmin.email, role: msAdmin.role, + isDisabled: msAdmin.deleted || false, }; return responseHandler( diff --git a/controllers/msAdminsController/organizations/getAllOrganizations.js b/controllers/msAdminsController/organizations/getAllOrganizations.js index a5ae7dc..8fbb462 100644 --- a/controllers/msAdminsController/organizations/getAllOrganizations.js +++ b/controllers/msAdminsController/organizations/getAllOrganizations.js @@ -1,6 +1,11 @@ const OrganizationsModel = require("../../../models/organizations"); +const MsAdmin = require("../../../models/msadmins"); const CustomError = require("../../../utils/customError"); const responseHandler = require("../../../utils/responseHandler"); +const { + getAllRecords, + getDeletedRecords, +} = require("../../../utils/softDelete"); /** * @author Ekeyekwu Oscar @@ -15,20 +20,39 @@ const getAllOrganizations = async (req, res, next) => { //get all organizations and map field names appropriately let allOrganizations; + //get msAdminId from token + const { msAdminId } = req.token; + + //check if msAdmin exists + const msAdmin = await MsAdmin.findById(msAdminId); + if (!msAdmin) { + next(new CustomError(404, "MsAdmin account not found")); + return; + } + let query = {}; + const { page, limit } = req.paginateOptions; + const { filter } = req.query; + try { //get all organizations - const organizations = await OrganizationsModel.paginate( - query, - req.paginateOptions - ); + let organizations; + if (filter === "disabled") { + organizations = await getDeletedRecords(OrganizationsModel, page, limit); + } else if (filter === "all") { + organizations = await getAllRecords(OrganizationsModel, page, limit); + } else { + organizations = await OrganizationsModel.paginate(query, { + ...req.paginateOptions, + }); + } allOrganizations = organizations.docs.map((organization) => { return { organizationId: organization._id, organizationName: organization.name, - organizationEmail: organization.email, + isBlocked: organization.deleted || false, }; }); diff --git a/controllers/msAdminsController/organizations/getSingleOrganization.js b/controllers/msAdminsController/organizations/getSingleOrganization.js index 4ae8d9f..f9fa072 100644 --- a/controllers/msAdminsController/organizations/getSingleOrganization.js +++ b/controllers/msAdminsController/organizations/getSingleOrganization.js @@ -29,9 +29,9 @@ const getSingleOrganization = async (req, res, next) => { } //get organizations - const singleOrganization = await OrganizationsModel.findById( - organizationId - ); + const singleOrganization = await OrganizationsModel.findOneWithDeleted({ + _id: organizationId, + }); if (!singleOrganization) { next(new CustomError(404, "Organization not Found")); @@ -42,6 +42,7 @@ const getSingleOrganization = async (req, res, next) => { organizationId: singleOrganization._id, organizationName: singleOrganization.name, organizationEmail: singleOrganization.email, + isBlocked: singleOrganization.deleted || false, }; } catch (error) { next(new CustomError(400, "An error occured retrieving organization")); diff --git a/middleware/requestLogger.js b/middleware/requestLogger.js index 117ac90..0889f10 100644 --- a/middleware/requestLogger.js +++ b/middleware/requestLogger.js @@ -36,8 +36,8 @@ exports.requestLogger = (req, res, next) => { res.on("finish", () => { // check if logging is enabled globally - const loggingEnabled = process.env.LOGGING_ENABLED - ? process.env.LOGGING_ENABLED.toLowerCase() === "true" + const loggingEnabled = process.env.loggingEnabled + ? process.env.loggingEnabled.toLowerCase() === "true" : false; try { diff --git a/models/systemSettings.js b/models/systemSettings.js index 6c868fd..db3d02d 100644 --- a/models/systemSettings.js +++ b/models/systemSettings.js @@ -29,6 +29,16 @@ const SystemSettingsSchema = new Schema( required: true, default: false, }, + loggingEnabled: { + type: Boolean, + required: true, + default: true, + }, + logPageSize: { + type: Number, + required: true, + default: 100, + }, }, { timestamps: true, diff --git a/settingAndVariables.txt b/settingAndVariables.txt index 412a01b..130a4db 100644 --- a/settingAndVariables.txt +++ b/settingAndVariables.txt @@ -20,7 +20,7 @@ SYSTEM SETTINGS [appear as ENV VARS at runtime] - maxRequestsPerMin - global max request rate to prevent DDoS and abuse, even plans cant exceed - defaultMaxRequestsPerDay - for making requests without plan (might be shelved in favor of application default plan) - disableRequestLimits - Disable all limiting, majorly for testing purposes only or open systems - - LOGGING_ENABLED - globally disable logging (make camel-case) + - LOGGING_ENABLED - now loggingEnabled - globally disable logging (make camel-case) - maxItemsPerpage - max items per request page - defaultItemsPerPage - default items per page when not set - logPageSize - The size of records from log diff --git a/utils/auth/tokenGenerator.js b/utils/auth/tokenGenerator.js index 33e24f9..fdaf492 100644 --- a/utils/auth/tokenGenerator.js +++ b/utils/auth/tokenGenerator.js @@ -102,7 +102,7 @@ const getSysToken = async (msAdminId) => { // confirm adminId exists const msAdmin = await MsAdmin.findById(msAdminId); if (!msAdmin) { - console.log(msAdminId, msAdmin); + // console.log(msAdminId, msAdmin); throw new CustomError( 401, "You are not authorized to access this resource" diff --git a/utils/paginateDeleted.js b/utils/paginateDeleted.js new file mode 100644 index 0000000..45fb800 --- /dev/null +++ b/utils/paginateDeleted.js @@ -0,0 +1,73 @@ +exports.paginateDeleted = async (Model, findFilter, query, limit, page) => { + let currentPage, + totalPages, + hasNext, + hasPrev, + nextPage, + prevPage, + pageRecordCount, + totalRecords, + docs; + + //get total documents in collection + if (findFilter === "enabled") { + totalRecords = await Model.countDocuments(query); + } else if (findFilter === "disabled") { + totalRecords = await Model.countDocumentsDeleted(query); + } else if (findFilter === "all") { + totalRecords = await Model.countDocumentsWithDeleted(query); + } else { + //throw an error here + throw new Error("Invalid 'findFilter' specified"); + } + + //divide total by limit(round up) to get number of pages + totalPages = Math.ceil(totalRecords / limit); + + //if requested page is greater than last page return last page with + currentPage = page; + + hasNext = true; + hasPrev = true; + nextPage = page + 1; + prevPage = page - 1; + + if (page >= totalPages) { + currentPage = totalPages; + hasNext = false; + nextPage = null; + } + + if (page <= 1) { + page = 1; + hasPrev = false; + prevPage = null; + } + + const skipCount = limit * (currentPage - 1); + + //skip (limit * page - 1) records and get limit records + let result; + if (findFilter === "enabled") { + result = await Model.find(query).skip(skipCount).limit(limit); + } else if (findFilter === "disabled") { + result = await Model.findDeleted(query).skip(skipCount).limit(limit); + } else if (findFilter === "all") { + result = await Model.findWithDeleted(query).skip(skipCount).limit(limit); + } + + pageRecordCount = result.length; + docs = result; + + return { + currentPage, + totalPages, + hasNext, + hasPrev, + nextPage, + prevPage, + pageRecordCount, + totalRecords, + docs, + }; +}; diff --git a/utils/softDelete.js b/utils/softDelete.js index 5b26788..263cf2f 100644 --- a/utils/softDelete.js +++ b/utils/softDelete.js @@ -1,3 +1,5 @@ +const { paginateDeleted } = require("./paginateDeleted"); + exports.deleteById = async (Model, targetDocId, deletedById) => { const targetDoc = await Model.findById(targetDocId); if (!targetDoc) { @@ -27,3 +29,13 @@ exports.restoreById = async (Model, targetDocId) => { }); }); }; + +exports.getDeletedRecords = async (Model, page = 1, limit = 20) => { + //get disabled records + return paginateDeleted(Model, "disabled", {}, limit, page); +}; + +exports.getAllRecords = async (Model, page = 1, limit = 20) => { + //get disabled records + return paginateDeleted(Model, "all", {}, limit, page); +}; diff --git a/utils/validationRules/msadmins/settings/updateSystemSettingsSchema.js b/utils/validationRules/msadmins/settings/updateSystemSettingsSchema.js index c036d8b..6aa5097 100644 --- a/utils/validationRules/msadmins/settings/updateSystemSettingsSchema.js +++ b/utils/validationRules/msadmins/settings/updateSystemSettingsSchema.js @@ -11,6 +11,8 @@ const updateSystemSettingsSchema = { maxItemsPerPage: Joi.number(), defaultMaxRequestsPerDay: Joi.number(), disableRequestLimits: Joi.boolean(), + loggingEnabled: Joi.boolean(), + logPageSize: Joi.number(), }), };