diff --git a/api-docs/openapi.json b/api-docs/openapi.json index 1044a3c4a..5fd0bee55 100644 --- a/api-docs/openapi.json +++ b/api-docs/openapi.json @@ -4788,6 +4788,122 @@ } } }, + "/conversation/{uuid}": { + "put": { + "tags": [ + "Conversation" + ], + "summary": "Updates a conversation by UUID (accessible to Secretariat only)", + "description": "
User must belong to an organization with the Secretariat role
Secretariat: Updates the conversation with the specified UUID
", + "operationId": "updateConversationByUUID", + "parameters": [ + { + "name": "uuid", + "in": "path", + "required": true, + "schema": { + "type": "string" + }, + "description": "The UUID of the conversation to update" + }, + { + "$ref": "#/components/parameters/apiEntityHeader" + }, + { + "$ref": "#/components/parameters/apiUserHeader" + }, + { + "$ref": "#/components/parameters/apiSecretHeader" + } + ], + "responses": { + "200": { + "description": "Returns the updated conversation", + "content": { + "application/json": { + "schema": { + "$ref": "../schemas/conversation/conversation.json" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "../schemas/errors/bad-request.json" + } + } + } + }, + "401": { + "description": "Not Authenticated", + "content": { + "application/json": { + "schema": { + "$ref": "../schemas/errors/generic.json" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "../schemas/errors/generic.json" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "../schemas/errors/generic.json" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "../schemas/errors/generic.json" + } + } + } + } + }, + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "body": { + "type": "string", + "description": "The updated content of the conversation message" + }, + "visibility": { + "type": "string", + "enum": [ + "private", + "public" + ], + "description": "The updated visibility of the conversation message" + } + } + } + } + } + } + } + }, "/review/byUUID/{uuid}": { "get": { "tags": [ diff --git a/schemas/registry-org/RootOrg.json b/schemas/registry-org/RootOrg.json index 9015ebdf5..d4c090444 100644 --- a/schemas/registry-org/RootOrg.json +++ b/schemas/registry-org/RootOrg.json @@ -16,6 +16,24 @@ "maxLength": 32 }, "aliases": { "$ref": "/BaseOrg#/properties/aliases" }, + "private_contacts": { + "type": "array", + "items": { + "type": "object", + "properties": { + "phone": { + "type": "string" + }, + "poc": { + "type": "string" + }, + "poc_email": { + "type": "string" + } + }, + "additionalProperties": false + } + }, "contact_info": { "type": "object", "properties": { @@ -38,8 +56,7 @@ "type": "array", "uniqueItems": true, "items": { - "type": "string", - "format": "uuid" + "$ref": "/BaseOrg#/definitions/uuidType" } }, "partner_role_type": { @@ -54,9 +71,7 @@ "advisory_locations": { "$ref": "/BaseOrg#/properties/advisory_locations" }, "program_data": { "$ref": "/BaseOrg#/properties/program_data" }, "industry": { "$ref": "/BaseOrg#/properties/industry" }, - "top_level_root": { "$ref": "/BaseOrg#/properties/top_level_root" }, - "tl_root_start_date": { "$ref": "/BaseOrg#/properties/tl_root_start_date" }, - "is_cna_discussion_list": { "$ref": "/BaseOrg#/properties/is_cna_discussion_list" } + "top_level_root": { "$ref": "/BaseOrg#/properties/top_level_root" } }, "required": ["short_name"] } diff --git a/src/model/baseorg.js b/src/model/baseorg.js index 1f59c2cc7..13777a5af 100644 --- a/src/model/baseorg.js +++ b/src/model/baseorg.js @@ -21,6 +21,7 @@ const schema = { website: String }, private_contacts: [{ + _id: false, phone: String, poc: String, poc_email: String diff --git a/src/repositories/baseOrgRepository.js b/src/repositories/baseOrgRepository.js index da27ff3d6..32ab42b20 100644 --- a/src/repositories/baseOrgRepository.js +++ b/src/repositories/baseOrgRepository.js @@ -807,10 +807,15 @@ class BaseOrgRepository extends BaseRepository { jointApprovalFields = getConstants().JOINT_APPROVAL_FIELDS } + // Convert Mongoose docs to plain objects and serialize to match stringified formats + const originalPlain = orgObjectOriginal.toObject ? orgObjectOriginal.toObject() : orgObjectOriginal + const originalSerialized = JSON.parse(JSON.stringify(originalPlain)) + const updatedSerialized = JSON.parse(JSON.stringify(orgObjectUpdated)) + // Filter the list to find only fields that have changed const changedFields = _.filter(jointApprovalFields, field => { // Check if the value in the original object is different from the updated object - return _.get(orgObjectOriginal, field) !== _.get(orgObjectUpdated, field) + return !_.isEqual(_.get(originalSerialized, field), _.get(updatedSerialized, field)) }) // Return the array of fields that had changes (will be empty if none changed) @@ -932,13 +937,13 @@ class BaseOrgRepository extends BaseRepository { // Check if there are actual changes to joint approval fields compared to current org object (not current review) // Only compare fields that are actually in the incoming data const incomingJointApprovalKeys = Object.keys(_.pick(registryObjectRaw, jointApprovalFieldsRegistry)) - const currentJointApprovalData = _.pick(registryOrg.toObject(), incomingJointApprovalKeys) - const incomingJointApprovalData = _.pick(registryObjectRaw, incomingJointApprovalKeys) + const currentJointApprovalData = JSON.parse(JSON.stringify(_.pick(registryOrg.toObject(), incomingJointApprovalKeys))) + const incomingJointApprovalData = JSON.parse(JSON.stringify(_.pick(registryObjectRaw, incomingJointApprovalKeys))) const hasJointApprovalChanges = !_.isEqual(currentJointApprovalData, incomingJointApprovalData) if (hasJointApprovalChanges) { // write the joint approval to the database - jointApprovalRegistry = _.merge({}, registryOrg.toObject(), registryObjectRaw) + jointApprovalRegistry = _.mergeWith({}, registryOrg.toObject(), registryObjectRaw, skipNulls) if (reviewObject) { await reviewObjectRepo.updateReviewOrgObject(jointApprovalRegistry, reviewObject.uuid, options) } else { diff --git a/src/utils/utils.js b/src/utils/utils.js index 82e022ec7..d30c4252d 100644 --- a/src/utils/utils.js +++ b/src/utils/utils.js @@ -309,8 +309,7 @@ function deepRemoveEmpty (obj) { // This will catch both initially empty fields and nested objects that became empty. if ( value === null || - (_.isObject(value) && !_.isDate(value) && _.isEmpty(value)) || - (_.isArray(value) && _.isEmpty(value)) + (_.isObject(value) && !_.isArray(value) && !_.isDate(value) && _.isEmpty(value)) ) { delete currentObj[key] } diff --git a/test/integration-tests/conversation/editConversationTest.js b/test/integration-tests/conversation/editConversationTest.js index 24b1ae440..9697a2173 100644 --- a/test/integration-tests/conversation/editConversationTest.js +++ b/test/integration-tests/conversation/editConversationTest.js @@ -31,6 +31,8 @@ describe('Testing Conversation edit by index endpoint', () => { delete org.admins delete org.users delete org.top_level_root + delete org.oversees + delete org.program_data }) await chai diff --git a/test/integration-tests/registry-org/registryOrgCRUDTest.js b/test/integration-tests/registry-org/registryOrgCRUDTest.js index ee5dd557d..2c49f3a63 100644 --- a/test/integration-tests/registry-org/registryOrgCRUDTest.js +++ b/test/integration-tests/registry-org/registryOrgCRUDTest.js @@ -71,6 +71,8 @@ describe('Testing /registryOrg endpoints', () => { createdOrg = res.body.created delete createdOrg.created delete createdOrg.last_updated + delete createdOrg.users + delete createdOrg.admins }) }) }) diff --git a/test/integration-tests/registry-org/rootOrgTest.js b/test/integration-tests/registry-org/rootOrgTest.js index 0ea61393a..cbb251087 100644 --- a/test/integration-tests/registry-org/rootOrgTest.js +++ b/test/integration-tests/registry-org/rootOrgTest.js @@ -33,6 +33,9 @@ describe('Testing ROOT Organization Type', () => { delete createdOrg.created delete createdOrg.last_updated delete createdOrg.program_data + delete createdOrg.users + delete createdOrg.admins + delete createdOrg.oversees }) }) @@ -85,7 +88,7 @@ describe('Testing ROOT Organization Type', () => { long_name: 'Updated Root Org Test' }) .then((res) => { - if (res.status === 400) console.log(JSON.stringify(res.body, null, 2)) + if (res.status !== 200) console.log('403 Response:', JSON.stringify(res.body, null, 2)) expect(res).to.have.status(200) }) })