Skip to content
183 changes: 166 additions & 17 deletions module/userProjects/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ module.exports = class UserProjectsHelper {
[
'_id',
'tasks',
'taskSequence',
'programInformation._id',
'solutionInformation._id',
'solutionInformation.externalId',
Expand Down Expand Up @@ -375,35 +376,183 @@ module.exports = class UserProjectsHelper {

updateProject.tasks = await _projectTask(data.tasks)

// Add metaInformation for custom tasks right after _projectTask processing
updateProject.tasks.forEach((task) => {
if (task.isACustomTask === true || task.isACustomTask === 'true') {
if (!task.metaInformation) {
task.metaInformation = {}
}
// Use buttonLabel from request if present, else default to "Upload"
task.metaInformation.buttonLabel = task.metaInformation.buttonLabel || 'Upload'
// Use icon from request if present, else default to "Upload"
task.metaInformation.icon = task.metaInformation.icon || 'Upload'
}
})

if (userProject[0].tasks && userProject[0].tasks.length > 0) {
// Helper function to recursively find a task by _id in nested structure
// This searches in both existing tasks and newly processed tasks
const findTaskById = (tasks, taskId) => {
for (let i = 0; i < tasks.length; i++) {
// Compare as strings to handle ObjectId vs string
if (String(tasks[i]._id) === String(taskId)) {
return tasks[i]
}
// Recursively search in children
if (tasks[i].children && tasks[i].children.length > 0) {
const found = findTaskById(tasks[i].children, taskId)
if (found) {
return found
}
}
}
return null
}

// Helper function to add child to parent's children array and update taskSequence
const addChildToParent = (parentTask, childTask) => {
// Initialize children array if it doesn't exist
if (!parentTask.children) {
parentTask.children = []
}

// Check if child already exists in parent's children
const childIndex = parentTask.children.findIndex(
(child) => String(child._id) === String(childTask._id)
)

if (childIndex < 0) {
// Add child to parent's children array
parentTask.children.push(childTask)

// Initialize taskSequence if it doesn't exist
if (!parentTask.taskSequence) {
parentTask.taskSequence = []
}

// Add child's externalId to parent's taskSequence if not already present
if (childTask.externalId && !parentTask.taskSequence.includes(childTask.externalId)) {
parentTask.taskSequence.push(childTask.externalId)
}
return true
} else {
// Child already exists, update it
parentTask.children[childIndex] = childTask
// Ensure externalId is in taskSequence
if (!parentTask.taskSequence) {
parentTask.taskSequence = []
}
if (childTask.externalId && !parentTask.taskSequence.includes(childTask.externalId)) {
parentTask.taskSequence.push(childTask.externalId)
}
return true
}
}

// Process tasks in two passes:
// Pass 1: Merge all tasks first (so we can find parents that are also being updated)
// Pass 2: Handle parent-child relationships

// Helper function to recursively find and update a task by _id at any nesting level
const findAndUpdateTaskRecursively = (tasks, taskToUpdate) => {
for (let i = 0; i < tasks.length; i++) {
// Compare as strings to handle ObjectId vs string
if (String(tasks[i]._id) === String(taskToUpdate._id)) {
// Found the task - update it
let keepFieldsFromTask = ['observationInformation', 'submissions']

removeFieldsFromRequest.forEach((removeField) => {
delete tasks[i][removeField]
})

keepFieldsFromTask.forEach((field) => {
if (tasks[i][field]) {
taskToUpdate[field] = tasks[i][field]
}
})

// Merge existing task data with updates
tasks[i] = {
...tasks[i],
...taskToUpdate,
updatedBy: userId,
updatedAt: new Date(),
}
return true
}
// Recursively search in children
if (tasks[i].children && tasks[i].children.length > 0) {
if (findAndUpdateTaskRecursively(tasks[i].children, taskToUpdate)) {
return true
}
}
}
return false
}

// First pass: Merge tasks into userProject[0].tasks (recursively find and update existing tasks)
updateProject.tasks.forEach((task) => {
task.updatedBy = userId
task.updatedAt = new Date()

let taskIndex = userProject[0].tasks.findIndex(
(projectTask) => projectTask._id === task._id
)
// Normalize parentId (handle space before parentId if present)
if (task[' parentId']) {
task.parentId = task[' parentId']
delete task[' parentId']
}

// Try to find and update the task recursively (at any nesting level)
const taskFound = findAndUpdateTaskRecursively(userProject[0].tasks, task)

if (!taskFound) {
// Task not found - it's a new task, treat as custom task
// Mark as custom task if not already set
if (task.isACustomTask !== true && task.isACustomTask !== 'true') {
task.isACustomTask = true
}

// Add metaInformation for all new custom tasks
if (!task.metaInformation) {
task.metaInformation = {}
}
// Use buttonLabel from request if present, else default to "Upload"
task.metaInformation.buttonLabel = task.metaInformation.buttonLabel || 'Upload'
// Use icon from request if present, else default to "Upload"
task.metaInformation.icon = task.metaInformation.icon || 'Upload'

if (taskIndex < 0) {
userProject[0].tasks.push(task)
} else {
let keepFieldsFromTask = ['observationInformation', 'submissions']
}
})

removeFieldsFromRequest.forEach((removeField) => {
delete userProject[0].tasks[taskIndex][removeField]
})
// Second pass: Handle parent-child relationships and update root taskSequence
// Now that all tasks are merged, we can find parents at any level

keepFieldsFromTask.forEach((field) => {
if (userProject[0].tasks[taskIndex][field]) {
task[field] = userProject[0].tasks[taskIndex][field]
}
})
// Initialize root taskSequence if it doesn't exist
if (!userProject[0].taskSequence) {
userProject[0].taskSequence = []
}

userProject[0].tasks[taskIndex] = task
updateProject.tasks.forEach((task) => {
if (task.parentId) {
// Find parent task recursively in the merged structure
// This will find parents at any nesting level (root, child, grandchild, etc.)
const parentTask = findTaskById(userProject[0].tasks, task.parentId)

if (parentTask) {
// Add child to parent's children array and update taskSequence
addChildToParent(parentTask, task)
}
} else {
// Root-level task (no parentId) - add to project's root taskSequence
if (task.externalId && !userProject[0].taskSequence.includes(task.externalId)) {
userProject[0].taskSequence.push(task.externalId)
}
}
})

updateProject.tasks = userProject[0].tasks
// Update root taskSequence in updateProject
updateProject.taskSequence = userProject[0].taskSequence
}

taskReport.total = updateProject.tasks.length
Expand Down Expand Up @@ -4906,9 +5055,9 @@ async function _projectTask(
singleTask.solutionDetails._id,
'',
{
name: `${singleTask.solutionDetails.name}-${timestamp}`,
name: `${singleTask.solutionDetails.name}`,
externalId: `${singleTask.solutionDetails.externalId}-${timestamp}`,
description: `${singleTask.solutionDetails.name}-${timestamp}`,
description: `${singleTask.solutionDetails.name}`,
programExternalId: programId,
status: CONSTANTS.common.PUBLISHED_STATUS,
tenantData: userDetails.tenantAndOrgInfo,
Expand Down
25 changes: 6 additions & 19 deletions release-docs/release-v3.4.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,25 +27,8 @@ This document outlines the detailed deployment steps, environment changes, migra

```json
{
"API_DOC_URL": "/interface/api-doc",
"APPLICATION_ENV": "production",
"APPLICATION_PORT": "3567",
"DEBUG_MODE": true,
"ENTITY_SERVICE_BASE_URL": "http://localhost:5002",
"MENTORING_SERVICE_BASE_URL": "http://localhost:7101",
"NOTIFICATION_SERVICE_BASE_URL": "http://localhost:7201",
"PROJECT_SERVICE_BASE_URL": "http://localhost:5003",
"RATE_LIMITER_ENABLED": true,
"RATE_LIMITER_GENERAL_LIMIT": 50,
"RATE_LIMITER_NUMBER_OF_PROXIES": 3,
"RATE_LIMITER_PUBLIC_LOW_LIMIT": 5,
"REQUIRED_BASE_PACKAGES": "project,self-creation-portal,survey",
"REQUIRED_PACKAGES": "elevate-project@1.1.34 elevate-self-creation-portal@1.0.64 elevate-survey-observation@1.0.3",
"ROUTE_CONFIG_JSON_URLS_PATHS": "https://raw.githubusercontent.com/ELEVATE-Project/utils/refs/heads/master/interface-routes/elevate-routes.json",
"SCHEDULER_SERVICE_BASE_URL": "http://localhost:7401",
"SUPPORTED_HTTP_TYPES": "GET POST PUT PATCH DELETE",
"SURVEY_SERVICE_BASE_URL": "http://localhost:5007",
"USER_SERVICE_BASE_URL": "http://localhost:7001"
"REQUIRED_PACKAGES": "elevate-project@1.1.53 elevate-survey-observation@1.0.23", // update these values
"ROUTE_CONFIG_JSON_URLS_PATHS": "https://raw.githubusercontent.com/ELEVATE-Project/utils/refs/heads/master/interface-routes/elevate-routes.json"
}
```

Expand Down Expand Up @@ -160,6 +143,10 @@ node correctScopeOrgValues.js
# M3. Update program components
cd migrations/
node updateComponentsOfAllPrograms.js

# M4. Create default org policies
cd migrations/createOrgExtensions
node createOrgExtensions.js
```

### 3. PM2 Deployment
Expand Down