diff --git a/src/controllers/bmdashboard/BuildingUnits.json b/src/controllers/bmdashboard/BuildingUnits.json new file mode 100644 index 000000000..d6b336e7c --- /dev/null +++ b/src/controllers/bmdashboard/BuildingUnits.json @@ -0,0 +1,122 @@ +[ +{ + "unit":"Micrograms", + "category":"Material" +}, +{ + "unit": "Milligrams (mg)", + "category": "Material" +}, +{ + "unit": "Centigrams (cg)", + "category": "Material" +}, +{ + "unit": "Decigrams (dg)", + "category": "Material" +}, +{ + "unit": "Grams (g)", + "category": "Material" +}, +{ + "unit": "Dekagram (dag)", + "category": "Material" +}, +{ + "unit": "Hectogram (hg)", + "category": "Material" +}, +{ + "unit": "Kilograms", + "category": "Material" +}, +{ + "unit": "Cubic Centimeter (cm³)", + "category": "Material" +}, +{ + "unit": "Cubic Millimeter (mm³)", + "category": "Material" +}, +{ + "unit": "Cubic Yard (yd³)", + "category": "Material" +}, +{ + "unit": "Square Meter (m²)", + "category": "Material" +}, +{ + "unit": "Metric ton (t)", + "category": "Material" +}, +{ + "unit": "Pounds", + "category": "Material" +}, +{ + "unit": "Ounces", + "category": "Material" +}, +{ + "unit": "Carats", + "category": "Material" +}, +{ + "unit": "Stone", + "category": "Material" +}, +{ + "unit": "Bags", + "category": "Material" +}, +{ + "unit": "Numbers", + "category": "Material" +}, +{ + "unit": "Board Feet (BDFT)", + "category": "Material" +}, +{ + "unit": "Cords", + "category": "Material" +}, +{ + "unit": "Pieces", + "category": "Material" +}, +{ + "unit": "Roll (for rolled insulation)", + "category": "Material" +}, +{ + "unit": "Bundle (for shingles)", + "category": "Material" +}, +{ + "unit": "Meter (for wiring,pipes)", + "category": "Material" +}, +{ + "unit": "Tubes (for caulking)", + "category": "Material" +}, +{ + "unit": "Tons", + "category": "Material" +}, +{ + "unit": "Cubic Meter", + "category": "Material" +}, +{ + "unit": "Sand", + "category": "Material" +}, +{ + "unit": "FakeUnitForTesting", + "category": "Material" +} +] \ No newline at end of file diff --git a/src/controllers/bmdashboard/bmInventoryTypeController.js b/src/controllers/bmdashboard/bmInventoryTypeController.js index 76029a42b..2427ce984 100644 --- a/src/controllers/bmdashboard/bmInventoryTypeController.js +++ b/src/controllers/bmdashboard/bmInventoryTypeController.js @@ -1,3 +1,7 @@ +const fs = require('fs'); + +const filepath = 'src/controllers/bmdashboard/BuildingUnits.json'; + function bmInventoryTypeController(InvType, MatType, ConsType, ReusType, ToolType, EquipType) { async function fetchMaterialTypes(req, res) { try { @@ -11,6 +15,122 @@ function bmInventoryTypeController(InvType, MatType, ConsType, ReusType, ToolTyp } } + const fetchInvUnitsFromJson = async (req, res) => { + try { + fs.readFile(filepath, 'utf8', (err, data) => { + if (err) { + console.error('Error reading file:', err); + return; + } + + try { + const jsonData = JSON.parse(data); + res.status(200).send(jsonData); + } catch (parseError) { + console.error('Error parsing JSON:', parseError); + res.status(500).send(parseError); + } + }); + } catch (err) { + res.json(err); + } + }; + + + async function addMaterialType(req, res) { + const { + name, + description, + requestor: { requestorId }, + } = req.body; + const unit = req.body.unit || req.body.customUnit; + try { + MatType + .find({ name }) + .then((result) => { + if (result.length) { + res.status(409).send('Oops!! Material already exists!'); + } else { + const newDoc = { + category: 'Material', + name, + description, + unit, + createdBy: requestorId, + }; + MatType + .create(newDoc) + .then((results) => { + res.status(201).send(results); + if (req.body.customUnit) { + try { + // Add new unit to json file : src\controllers\bmdashboard\BuildingUnits.json + const newItem = { unit: req.body.customUnit, category: 'Material' }; + const newItemString = JSON.stringify(newItem, null, 2); + fs.readFile(filepath, 'utf8', (err, data) => { + if (err) { + console.error('Error reading file:', err); + return; + } + // Remove the last array bracket and comma + const updatedContent = data.trim().replace(/\s*]$/, ''); + + // Add a comma and newline if the file is not empty + const separator = (updatedContent !== '') ? ',\n' : ''; + const updatedFileContent = `${updatedContent}${separator}${newItemString}\n]`; + + fs.writeFile(filepath, updatedFileContent, 'utf8', (error) => { + if (error) { + console.error('Error writing to file:', error); + return; + } + }); + }); + } catch (e) { + console.log(e); + } + } + }) + .catch((error) => { + if (error._message.includes('validation failed')) { + res.status(400).send(error); + } else { + res.status(500).send(error); + } + }); + } + }) + .catch(error => res.status(500).send(error)); + } catch (error) { + res.status(500).send(error); + } + } + + async function fetchInventoryByType(req, res) { + const { type } = req.params; + let SelectedType = InvType; + if (type === 'Materials') { + SelectedType = MatType; + } else if (type === 'Consumables') { + SelectedType = ConsType; + } else if (type === 'Reusables') { + SelectedType = ReusType; + } else if (type === 'Tools') { + SelectedType = ToolType; + } else if (type === 'Equipments') { + SelectedType = EquipType; + } + try { + SelectedType + .find() + .exec() + .then(result => res.status(200).send(result)) + .catch(error => res.status(500).send(error)); + } catch (err) { + res.json(err); + } + } + async function addEquipmentType(req, res) { const { name, @@ -94,6 +214,9 @@ function bmInventoryTypeController(InvType, MatType, ConsType, ReusType, ToolTyp addEquipmentType, fetchSingleInventoryType, updateNameAndUnit, + addMaterialType, + fetchInvUnitsFromJson, + fetchInventoryByType, }; } diff --git a/src/controllers/bmdashboard/bmToolController.js b/src/controllers/bmdashboard/bmToolController.js index 0feec256c..f697dbd94 100644 --- a/src/controllers/bmdashboard/bmToolController.js +++ b/src/controllers/bmdashboard/bmToolController.js @@ -9,6 +9,10 @@ const bmToolController = (BuildingTool) => { path: 'itemType', select: '_id name description unit imageUrl category', }, + { + path: 'project', + select: 'name', + }, { path: 'userResponsible', select: '_id firstName lastName', diff --git a/src/controllers/permissionChangeLogsController.js b/src/controllers/permissionChangeLogsController.js new file mode 100644 index 000000000..317b9f144 --- /dev/null +++ b/src/controllers/permissionChangeLogsController.js @@ -0,0 +1,30 @@ +const UserProfile = require('../models/userProfile'); + +const permissionChangeLogController = function (PermissionChangeLog) { + + const getPermissionChangeLogs = async function (req, res) { + + try { + const userProfile = await UserProfile.findOne({ _id: req.params.userId }).exec() + + if (userProfile) { + if (userProfile.role !== 'Owner') { + res.status(204).send([]) + } else { + const changeLogs = await PermissionChangeLog.find({}) + res.status(200).send(changeLogs) + } + } else { + res.status(403).send(`User (${req.params.userId}) not found.`) + } + } catch (err) { + console.error(err) + } + } + + return { + getPermissionChangeLogs + } +} + +module.exports = permissionChangeLogController \ No newline at end of file diff --git a/src/models/permissionChangeLog.js b/src/models/permissionChangeLog.js new file mode 100644 index 000000000..3ca37e416 --- /dev/null +++ b/src/models/permissionChangeLog.js @@ -0,0 +1,26 @@ +const mongoose = require('mongoose'); +const { Schema } = mongoose; + +const User = require('./userProfile'); +const rolesMergedPermissions = require('./role') + +const PermissionChangeLog = new Schema({ + logDateTime: { type: String, required: true }, + roleId: { + type: mongoose.Types.ObjectId, + ref: rolesMergedPermissions, + required: true + }, + roleName: { type: String }, + permissions: { type: [String], required: true }, + permissionsAdded: { type: [String], required: true }, + permissionsRemoved: { type: [String], required: true }, + requestorId: { + type: mongoose.Types.ObjectId, + ref: User + }, + requestorRole: { type: String }, + requestorEmail: { type: String, required: true}, +}); + +module.exports = mongoose.model('permissionChangeLog', PermissionChangeLog, 'permissionChangeLogs'); \ No newline at end of file diff --git a/src/routes/bmdashboard/bmInventoryTypeRouter.js b/src/routes/bmdashboard/bmInventoryTypeRouter.js index e89cb6b74..787894dec 100644 --- a/src/routes/bmdashboard/bmInventoryTypeRouter.js +++ b/src/routes/bmdashboard/bmInventoryTypeRouter.js @@ -8,6 +8,12 @@ const routes = function (baseInvType, matType, consType, reusType, toolType, equ inventoryTypeRouter.route('/invtypes/materials') .get(controller.fetchMaterialTypes); + inventoryTypeRouter.route('/invtypes/material') + .post(controller.addMaterialType); + // Route for fetching types by selected type + inventoryTypeRouter.route('/invtypes/:type') + .get(controller.fetchInventoryByType); + inventoryTypeRouter.route('/invtypes/equipment') .post(controller.addEquipmentType); @@ -15,6 +21,10 @@ const routes = function (baseInvType, matType, consType, reusType, toolType, equ inventoryTypeRouter.route('/invtypes/material/:invtypeId') .get(controller.fetchSingleInventoryType) .put(controller.updateNameAndUnit); + + inventoryTypeRouter.route('/inventoryUnits') + .get(controller.fetchInvUnitsFromJson); + return inventoryTypeRouter; }; diff --git a/src/routes/permissionChangeLogsRouter.js b/src/routes/permissionChangeLogsRouter.js new file mode 100644 index 000000000..8c1f46219 --- /dev/null +++ b/src/routes/permissionChangeLogsRouter.js @@ -0,0 +1,14 @@ +const express = require('express'); + +const routes = function (permissionChangeLog) { + const controller = require('../controllers/permissionChangeLogsController')(permissionChangeLog) + + const permissionChangeLogRouter = express.Router() + + permissionChangeLogRouter.route("/permissionChangeLogs/:userId") + .get(controller.getPermissionChangeLogs) + + return permissionChangeLogRouter +} + +module.exports = routes \ No newline at end of file diff --git a/src/routes/roleRouter.js b/src/routes/roleRouter.js index 8c5164f12..c9d4f963f 100644 --- a/src/routes/roleRouter.js +++ b/src/routes/roleRouter.js @@ -1,4 +1,5 @@ const express = require('express'); +const changedPermissionsLogger = require('../utilities/logPermissionChangeByAccount') const routes = function (role) { const controller = require('../controllers/rolesController')(role); @@ -10,7 +11,7 @@ const routes = function (role) { RolesRouter.route('/roles/:roleId') .get(controller.getRoleById) - .patch(controller.updateRoleById) + .patch(changedPermissionsLogger,controller.updateRoleById) .delete(controller.deleteRoleById); return RolesRouter; }; diff --git a/src/startup/routes.js b/src/startup/routes.js index 4701a9f61..1860d7060 100644 --- a/src/startup/routes.js +++ b/src/startup/routes.js @@ -21,11 +21,10 @@ const weeklySummaryAIPrompt = require('../models/weeklySummaryAIPrompt'); const profileInitialSetuptoken = require('../models/profileInitialSetupToken'); const reason = require('../models/reason'); const mouseoverText = require('../models/mouseoverText'); -// const inventoryItemMaterial = require('../models/inventoryItemMaterial'); +const permissionChangeLog = require('../models/permissionChangeLog'); const mapLocations = require('../models/mapLocation'); const buildingProject = require('../models/bmdashboard/buildingProject'); const buildingNewLesson = require('../models/bmdashboard/buildingNewLesson'); -// const buildingMaterial = require('../models/bmdashboard/buildingMaterial'); const { invTypeBase, materialType, @@ -37,8 +36,9 @@ const { const { buildingConsumable, buildingMaterial, + buildingTool, } = require('../models/bmdashboard/buildingInventoryItem'); -const buildingTool = require('../models/bmdashboard/buildingTool'); +// const buildingTool = require('../models/bmdashboard/buildingTool'); const userProfileRouter = require('../routes/userProfileRouter')(userProfile); const badgeRouter = require('../routes/badgeRouter')(badge); @@ -61,9 +61,9 @@ const taskNotificationRouter = require('../routes/taskNotificationRouter')(taskN const inventoryRouter = require('../routes/inventoryRouter')(inventoryItem, inventoryItemType); const timeZoneAPIRouter = require('../routes/timeZoneAPIRoutes')(); const profileInitialSetupRouter = require('../routes/profileInitialSetupRouter')(profileInitialSetuptoken, userProfile, project, mapLocations); +const permissionChangeLogRouter = require('../routes/permissionChangeLogsRouter')(permissionChangeLog); const isEmailExistsRouter = require('../routes/isEmailExistsRouter')(); - const taskEditSuggestion = require('../models/taskEditSuggestion'); const taskEditSuggestionRouter = require('../routes/taskEditSuggestionRouter')(taskEditSuggestion); const roleRouter = require('../routes/roleRouter')(role); @@ -112,6 +112,7 @@ module.exports = function (app) { app.use('/api', reasonRouter); app.use('/api', informationRouter); app.use('/api', mouseoverTextRouter); + app.use('/api', permissionChangeLogRouter); app.use('/api', isEmailExistsRouter); app.use('/api', mapLocationRouter); // bm dashboard diff --git a/src/utilities/logPermissionChangeByAccount.js b/src/utilities/logPermissionChangeByAccount.js new file mode 100644 index 000000000..dac2a4016 --- /dev/null +++ b/src/utilities/logPermissionChangeByAccount.js @@ -0,0 +1,68 @@ +const moment = require("moment-timezone"); +const PermissionChangeLog = require("../models/permissionChangeLog") + +// Middleware function +const changedPermissionsLogger = async (req, res, next) => { + await logPermissionChangeByAccount(req.body) + next(); +}; + +// Helper function finds the latest log related to the permission +const findLatestRelatedLog = (roleId) => { + + return new Promise((resolve, reject) => { + PermissionChangeLog.findOne({ roleId: roleId }) + .sort({ logDateTime: -1 }) + .exec((err, document) => { + if (err) { + console.error(err); + reject(err); + return; + } + resolve(document); + }); + }) +} + +// Function saves logs to hgnData_dev.permissionChangeLogs collection +const logPermissionChangeByAccount = async (requestBody) => { + const { roleId, roleName, permissions, requestor, role, email } = requestBody + const dateTime = moment().tz("America/Los_Angeles").format(); + + try { + let permissionsAdded = [] + let permissionsRemoved = [] + + // Find the latest log related to permission + const document = await findLatestRelatedLog(roleId) + + if (document) { + permissionsRemoved = document.permissions.filter(item => !(permissions.includes(item))) + permissionsAdded = permissions.filter(item => !(document.permissions.includes(item))) + } else { + // else this is the first permissions change log for this particular role + permissionsAdded = permissions + } + + + const logEntry = new PermissionChangeLog({ + logDateTime: dateTime, + roleId: roleId, + roleName: roleName, + permissions: permissions, + permissionsAdded: permissionsAdded, + permissionsRemoved: permissionsRemoved, + requestorId: requestor.requestorId, + requestorRole: role, + requestorEmail: email, + }) + + await logEntry.save() + + } catch (error) { + console.error('Error logging permission change:', error); + res.status(500).json({ error: 'Failed to log permission change' }); + } +} + +module.exports = changedPermissionsLogger;