Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
21 changes: 21 additions & 0 deletions src/controller/registry-org.controller/error.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,27 @@ class RegistryOrgControllerError extends idrErr.IDRError {
err.message = 'Parameters were invalid: conversation must be an object with a body.'
return err
}

conversationDne (shortname, index) {
const err = {}
err.error = 'CONVERSATION_DNE'
err.message = `The conversation at index ${index} does not exist for the ${shortname} organization.`
return err
}

notAllowedToEditConversation () {
const err = {}
err.error = 'NOT_ALLOWED_TO_EDIT_CONVERSATION'
err.message = 'You must be the original author or Secretariat to edit this conversation.'
return err
}

notAllowedToChangeConversationVisibility () {
const err = {}
err.error = 'NOT_ALLOWED_TO_CHANGE_CONVERSATION_VISIBILITY'
err.message = 'Only the Secretariat is allowed to change the visibility of a conversation.'
return err
}
}

module.exports = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -594,12 +594,96 @@ async function createUserByOrg (req, res, next) {
}
}

/**
* Updates the conversation at the provided index for the given organization.
*
* @async
* @function editConversationForOrg
* @param {object} req - The Express request object, containing the organization shortname in `req.ctx.params.shortname` and conversation updates in `req.ctx.body`.
* @param {object} res - The Express response object.
* @param {function} next - The next middleware function.
* @returns {Promise<void>} - A promise that resolves when the response is sent.
* @description User must be the original author of the conversation or the Secretariat role.
* The original author is allowed to update the conversation message body.
* Secretariat is allowed to update the conversation message body and visibility.
* Called by PUT /api/registryOrg/:shortname/conversation/:index
*/
async function editConversationForOrg (req, res, next) {
const orgRepo = req.ctx.repositories.getBaseOrgRepository()
const userRepo = req.ctx.repositories.getBaseUserRepository()
const conversationRepo = req.ctx.repositories.getConversationRepository()
const requesterUsername = req.ctx.user
const orgShortName = req.ctx.params.shortname
const index = req.params.index
const incomingParameters = req.ctx.body
let returnValue

const session = await mongoose.startSession({ causalConsistency: false })
try {
// Check if org exists
const orgUUID = await orgRepo.getOrgUUID(orgShortName, {}, false)
if (!orgUUID) {
logger.info({ uuid: req.ctx.uuid, message: 'The conversation could not be edited because ' + orgShortName + ' organization does not exist.' })
return res.status(404).json(error.orgDnePathParam(orgShortName))
}

try {
session.startTransaction()
// Fetch conversation
const conversation = conversationRepo.findByTargetUUIDAndIndex(orgUUID, index, { session })
if (!conversation) {
logger.info({ uuid: req.ctx.uuid, message: `The conversation at index ${index} does not exist for the ${orgShortName} organization.` })
return res.status(404).json(error.conversationDne(orgShortName, index))
}

// Check if user has permissions to edit conversation
const isSecretariat = await orgRepo.isSecretariatByShortName(req.ctx.org, { session })
const userUUID = await userRepo.getUserUUID(requesterUsername, orgShortName, { session })
if (conversation.author_id !== userUUID && !isSecretariat) {
logger.info({ uuid: req.ctx.uuid, message: 'The user does not have permission to edit this conversation.' })
return res.status(403).json(error.notAllowedToEditConversation())
}

// Check if user has permission to change visibility of conversation
if (incomingParameters.visibility && !isSecretariat) {
logger.info({ uuid: req.ctx.uuid, message: 'Only the Secretariat is allowed to change the visibility of a conversation.' })
return res.status(403).json(error.notAllowedToChangeConversationVisibility())
}

// Make the edit
returnValue = await conversationRepo.editConversation(conversation.UUID, incomingParameters, userUUID, { session })
} catch (error) {
await session.abortTransaction()
throw error
} finally {
await session.endSession()
}

const responseMessage = {
message: 'The conversation was successfully updated.',
updated: returnValue
}

const payload = {
action: 'update_org_conversation',
change: `Conversation at index ${index} for org ${orgShortName} was successfully updated.`,
req_UUID: req.ctx.uuid,
org_UUID: orgUUID
}
logger.info(JSON.stringify(payload))
return res.status(200).json(responseMessage)
} catch (err) {
next(err)
}
}

module.exports = {
ALL_ORGS: getAllOrgs,
SINGLE_ORG: getOrg,
CREATE_ORG: createOrg,
UPDATE_ORG: updateOrg,
DELETE_ORG: deleteOrg,
USER_ALL: getUsers,
USER_CREATE_SINGLE: createUserByOrg
USER_CREATE_SINGLE: createUserByOrg,
EDIT_CONVERSATION: editConversationForOrg
}
4 changes: 3 additions & 1 deletion src/model/conversation.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ const schema = {
author_role: String,
visibility: String,
body: String,
posted_at: Date
posted_at: Date,
edited_at: Date,
editor_id: String
}

const ConversationSchema = new mongoose.Schema(schema, { collection: 'Conversation', timestamps: { createdAt: 'posted_at', updatedAt: 'last_updated' } })
Expand Down
35 changes: 34 additions & 1 deletion src/repositories/conversationRepository.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,27 @@ class ConversationRepository extends BaseRepository {
}

async getAllByTargetUUID (targetUUID, isSecretariat, options = {}) {
const conversations = await ConversationModel.find({ target_uuid: targetUUID }, null, options)
const conversations = await ConversationModel.find({ target_uuid: targetUUID }, null, {
...options,
sort: {
posted_at: 1,
UUID: 1
}
})
return conversations.map(convo => convo.toObject()).filter(conv => isSecretariat || conv.visibility === 'public')
}

async findByTargetUUIDAndIndex (targetUUID, index, options = {}) {
const conversation = await ConversationModel.find({ target_uuid: targetUUID }, null, {
...options,
sort: {
posted_at: 1,
UUID: 1
}
}).skip(index).limit(1)
return conversation.toObject()
}

async createConversation (targetUUID, body, user, isSecretariat, options = {}) {
const { getUserFullName } = require('../utils/utils')
const newUUID = uuid.v4()
Expand All @@ -57,13 +74,29 @@ class ConversationRepository extends BaseRepository {
author_id: user.UUID,
author_name: getUserFullName(user),
author_role: isSecretariat ? 'Secretariat' : 'Partner',
editor_id: null,
edited_at: null,
visibility: !isSecretariat ? 'public' : (['public', 'private'].includes(body.visibility?.toLowerCase()) ? body.visibility.toLowerCase() : 'private'),
body: body.body
}
const newConversation = new ConversationModel(conversationObj)
const result = await newConversation.save(options)
return result.toObject()
}

async editConversation (UUID, incomingParameters, userUUID, options = {}) {
const conversation = this.findOneByUUID(UUID, options)
if (incomingParameters?.body) {
conversation.body = incomingParameters.body
}
if (incomingParameters?.visibility) {
conversation.visibility = incomingParameters.visibility
}
conversation.editor_id = userUUID
conversation.edited_at = Date.now()
const result = await conversation.save(options)
return result.toObject()
}
}

module.exports = ConversationRepository
Loading