diff --git a/api/src/paths/activities.ts b/api/src/paths/activities.ts index 4a3611870..e0346bd2d 100644 --- a/api/src/paths/activities.ts +++ b/api/src/paths/activities.ts @@ -6,11 +6,12 @@ import { ALL_ROLES, SECURITY_ON } from 'constants/misc'; import { streamActivitiesResult } from 'utils/iapp-json-utils'; import { getDBConnection } from 'database/db'; import { ActivitySearchCriteria } from 'models/activity'; -import { deleteActivitiesSQL, getActivitiesSQL } from 'queries/activity-queries'; +import { deleteActivitiesSQL, getActivitiesSQL, getLinkedMonitoringRecordsFromTreatmentSQL } from 'queries/activity-queries'; import { getLogger } from 'utils/logger'; import { InvasivesRequest } from 'utils/auth-utils'; import cacheService from 'utils/cache/cache-service'; import { versionedKey } from 'utils/cache/cache-utils'; +import { ActivityType } from 'sharedAPI'; const defaultLog = getLogger('activity'); const CACHENAME = 'Activities - Fat'; @@ -24,10 +25,10 @@ GET.apiDoc = { tags: ['activity'], security: SECURITY_ON ? [ - { - Bearer: ALL_ROLES - } - ] + { + Bearer: ALL_ROLES + } + ] : [], responses: { 200: { @@ -78,10 +79,10 @@ DELETE.apiDoc = { tags: ['activity'], security: SECURITY_ON ? [ - { - Bearer: ALL_ROLES - } - ] + { + Bearer: ALL_ROLES + } + ] : [], requestBody: { description: 'Delete activities', @@ -296,106 +297,98 @@ function getActivitiesBySearchFilterCriteria(): RequestHandler { /** * Soft-deletes all activity records based on a list of ids. - * * @return {RequestHandler} */ function deleteActivitiesByIds(): RequestHandler { return async (req: InvasivesRequest, res) => { - defaultLog.debug({ label: 'activity', message: 'deleteActivitiesByIds', body: req.body }); - - const sanitizedSearchCriteria = new ActivitySearchCriteria({ - keycloakToken: req.keycloakToken - }); - - const isAdmin = (req as any).authContext.roles.find((role) => role.role_id === 18) !== undefined; - const preferred_username = req.authContext.preferredUsername; - defaultLog.debug({ label: 'activity', message: 'roles for delete', body: (req as any).authContext.roles }); - defaultLog.debug({ - label: 'activity', - message: 'is admin delete', - body: { typeof: typeof isAdmin, value: JSON.stringify(isAdmin) } - }); - - const { ids } = req.body; - - sanitizedSearchCriteria.activity_ids = ids; - const connection = await getDBConnection(); - if (!connection) { - return res - .status(503) - .json({ message: 'Database connection unavailable', request: req.body, namespace: 'activities', code: 503 }); - } + try { + defaultLog.debug({ label: 'activity', message: '[deleteActivitiesByIds]', body: req.body }) - if (!isAdmin) { - const sqlStatement = getActivitiesSQL(sanitizedSearchCriteria, false); - defaultLog.debug({ label: 'activity', message: 'non admin delete', body: sqlStatement }); + const isMasterAdmin = (req as any).authContext.roles.some((role: Record) => role.role_id === 18) + const preferred_username = req.authContext.friendlyUsername; + const { ids } = req.body; - if (!sqlStatement) { - return res - .status(500) - .json({ message: 'Unable to generate SQL statement', request: req.body, namespace: 'activities', code: 500 }); - } + if (ids.length === 0) { + return res.status(400).json({ + message: 'No ids provided', request: req.body, namespace: 'activities', code: 400 + }); + }; - const response = await connection.query(sqlStatement.text, sqlStatement.values); + const sanitizedSearchCriteria = new ActivitySearchCriteria({ keycloakToken: req.keycloakToken }) + sanitizedSearchCriteria.activity_ids = ids; + sanitizedSearchCriteria.hideTreatmentsAndMonitoring = false; - let isAuthorized = true; - if (response.rows.length > 0) { - response.rows.forEach((row, i) => { - if (row.created_by_with_guid !== preferred_username) { - isAuthorized = false; - } + const sqlStatement: SQLStatement = getActivitiesSQL(sanitizedSearchCriteria, false); + const deleteSQLStatement: SQLStatement = deleteActivitiesSQL(ids, req); + if (!connection) { + return res.status(503).json({ + message: 'Database connection unavailable', request: req.body, namespace: 'activities', code: 503 }); - if (!isAuthorized) { - return res.status(401).json({ - message: 'Invalid request, user is not authorized to delete this record', // better message - request: req.body, - namespace: 'activities', - code: 401 + }; + if (!sqlStatement || !deleteSQLStatement) { + return res.status(500).json({ + message: 'Unable to generate SQL Statement', request: req.body, namespace: 'activities', code: 500 + }); + }; + + const recordsToDelete = await connection.query(sqlStatement.text, sqlStatement.values); + // Identify Treatment Records and check for any matching IDs, exit early if any exist. + const recordsWithTreatments = recordsToDelete.rows.filter((entry) => entry?.activity_type === ActivityType.Treatment); + for (const record of recordsWithTreatments) { + const sql = getLinkedMonitoringRecordsFromTreatmentSQL(record.activity_id); + const results = await connection.query(sql); + if (results.rowCount > 0) { + return res.status(403).json({ + message: `Cannot delete ${ActivityType.Treatment} record with linked ${ActivityType.Monitoring} record(s)`, + request: `${record.activity_id} contains linked ${ActivityType.Monitoring} record(s)`, + status: 403, + namespace: 'activities' }); } - } else { - return res.status(401).json({ - message: 'Invalid request nothing to delete', - request: req.body, - namespace: 'activities', - code: 401 - }); } - } - if (!ids || !ids.length) { - return res - .status(400) - .json({ message: 'Invalid request, no ids provided', request: req.body, namespace: 'activities', code: 400 }); - } + const userCreatedEntries = recordsToDelete.rows.every((entry) => ( + entry?.activity_payload?.created_by === preferred_username + )); - try { - const sqlStatement: SQLStatement = deleteActivitiesSQL(ids, req); - - if (!sqlStatement) { - return res - .status(500) - .json({ message: 'Unable to generate SQL statement', request: req.body, namespace: 'activities', code: 500 }); + if (recordsToDelete.rowCount === 0) { + return res.status(404).json({ + message: 'No ID\'s found matching request', request: req.body, namespace: 'activities', code: 404 + }); } + if (recordsToDelete.rowCount !== ids.length) { + return res.status(404).json({ + message: 'A record matching a supplied id was not found', request: req.body, namespace: 'activities', code: 404 + }); + }; - const response = await connection.query(sqlStatement.text, sqlStatement.values); - - return res.status(200).json({ - message: 'Deleted activities by ids', + if (isMasterAdmin || userCreatedEntries) { + const response = await connection.query(deleteSQLStatement.text, deleteSQLStatement.values); + return res.status(200).json({ + message: 'Deleted activities by ids', + request: req.body, + result: response.rows, + count: response.rowCount, + namespace: 'activities', + code: 200 + }); + }; + /* Future Specific-Role Handling Logic applied here */ + return res.status(401).json({ + message: 'Unauthorized Access', request: req.body, - result: response.rows, - count: response.rowCount, namespace: 'activities', - code: 200 + code: 401 }); - } catch (error) { - defaultLog.debug({ label: 'deleteActivitiesByIds', message: 'error', error }); - return res - .status(500) - .json({ message: 'Error deleting activities by ids', error, namespace: 'activities', code: 500 }); + + } catch (ex) { + defaultLog.error({ + label: 'activity', message: '[deleteActivitiesByIds]', body: ex + }); + return res.status(500); } finally { connection.release(); } - }; + } } diff --git a/api/src/queries/activity-queries.ts b/api/src/queries/activity-queries.ts index cd2ea7c31..0b950a2c0 100644 --- a/api/src/queries/activity-queries.ts +++ b/api/src/queries/activity-queries.ts @@ -874,6 +874,21 @@ export const deleteActivitiesSQL = (activityIds: Array, req?: any): SQLS return sqlStatement; }; +/** + * @desc SQL Query to get all monitoring records matching a Treatment record + * @param treatmentRecordID + * @returns { SQLStatement } + */ +export const getLinkedMonitoringRecordsFromTreatmentSQL = (treatmentRecordID: string): SQLStatement => ( + SQL` + SELECT activity_id + FROM activity_incoming_data + WHERE activity_type = 'Monitoring' + AND iscurrent = True + AND activity_payload->'form_data'->'activity_type_data'->>'linked_id' = ${treatmentRecordID}; + ` +); + /** * SQL query to un-delete activity records. *