Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
ae1875d
Task#251045 Feat: Hierarchical Categories Implementation
Sachintechjoomla Dec 11, 2025
8120df4
Issue#251045 Fix: Hierarchical Categories Implementation with templat…
Sachintechjoomla Dec 12, 2025
5e89a98
Task#251045 Feat: Remove unwanted programId field in category
Sachintechjoomla Dec 12, 2025
b21b2b7
Task#251045 Feat: Hierarchical Categories Implementation
Sachintechjoomla Dec 12, 2025
3fc347d
Task#251045 Feat: Hierarchical Categories Implementation
Sachintechjoomla Dec 12, 2025
592644c
Task#251045 Feat: Hierarchical Categories Implementation
Sachintechjoomla Dec 15, 2025
03cdaea
Task#251045 Feat: send tenant_code and orgid in token and decry pt it
Sachintechjoomla Dec 16, 2025
1edbf6f
Issue#251045 Feat: Delete Category > No children exist No templates r…
Sachintechjoomla Dec 16, 2025
8a08113
Task#251045 Feat: Hierarchical Categories Implementation
Sachintechjoomla Dec 19, 2025
b20d3b1
fix: Improve children array consistency across category hierarchy ope…
Sachintechjoomla Dec 22, 2025
1455c13
Task#251045 Feat: Hierarchical Categories Implementation
Sachintechjoomla Dec 23, 2025
9c52a9d
Task#251045 Feat: Hierarchical Categories Implementation
Sachintechjoomla Dec 23, 2025
d83009a
Task#251045 Feat: Hierarchical Categories Implementation
Sachintechjoomla Dec 23, 2025
67a95b0
Task#251045 Feat: Hierarchical Categories Implementation
Sachintechjoomla Dec 23, 2025
67dbabd
Task#251045 Feat: Hierarchical Categories Implementation
Sachintechjoomla Dec 23, 2025
05f28cf
Task#251045 Feat: Hierarchical Categories Implementation
Sachintechjoomla Dec 23, 2025
94d8ee9
Removed level and isLeaf fields from the categories array in project-…
Sachintechjoomla Dec 23, 2025
a0b8fce
Task#251045 Feat: Hierarchical Categories Implementation
Sachintechjoomla Dec 23, 2025
87a93a8
Task#251045 Feat: Hierarchical Categories Implementation
Sachintechjoomla Dec 24, 2025
7f21289
Task#251045 Feat: Hierarchical Categories Implementation
Sachintechjoomla Dec 24, 2025
8e0e520
removed the entire canDelete endpoint and all related code
Sachintechjoomla Dec 24, 2025
a5c549e
Move function removed archived this in update
Sachintechjoomla Dec 24, 2025
0821f94
for project support supports single or comma-separated IDs
Sachintechjoomla Dec 24, 2025
307fbb5
Task#251045 Feat: Hierarchical Categories Implementation
Sachintechjoomla Dec 24, 2025
b143dde
Task#251045 Feat: Hierarchical Categories Implementation
Sachintechjoomla Dec 24, 2025
8854fc3
Task#251045 Feat: Hierarchical Categories Implementation
Sachintechjoomla Dec 24, 2025
179f63c
Task#251045 Feat: Use same function for project list
Sachintechjoomla Dec 24, 2025
849572e
Task#251045 Feat: Hierarchical Categories Implementation
Sachintechjoomla Dec 24, 2025
4b38a35
Task#251045 Feat: Hierarchical Categories Implementation
Sachintechjoomla Dec 24, 2025
fbe7d8b
Task#251045 Feat: Hierarchical Categories Implementation
Sachintechjoomla Dec 24, 2025
d3014ac
Task#251045 Feat: Hierarchical Categories Implementation
Sachintechjoomla Dec 24, 2025
4f0518d
Task#251045 Feat: Hierarchical Categories Implementation
Sachintechjoomla Dec 29, 2025
dbd5e11
Task#251045 Feat: Hierarchical Categories Implementation
Sachintechjoomla Dec 29, 2025
354aff7
Task#251045 Feat: Hierarchical Categories Implementation
Sachintechjoomla Dec 29, 2025
d00b523
Task#251045 Feat: Hierarchical Categories Implementation
Sachintechjoomla Dec 29, 2025
58e3966
Task#251045 Feat: Hierarchical Categories Implementation
Sachintechjoomla Dec 29, 2025
8f4a223
update category details formatting
Sachintechjoomla Dec 29, 2025
f4d2b78
update category details formatting
Sachintechjoomla Dec 29, 2025
207ffe8
Task#251045 Feat: Hierarchical Categories Implementation
Sachintechjoomla Dec 29, 2025
5df4842
Task#251045 Feat: Hierarchical Categories Implementation
Sachintechjoomla Dec 30, 2025
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
6 changes: 5 additions & 1 deletion .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,8 @@ ORG_UPDATES_TOPIC = elevate_project_org_extension_event_listener

USER_ACCOUNT_EVENT_TOPIC = elevate_user_account_event_listener // Kafka topic to listen user account events
SESSION_VERIFICATION_METHOD = user_service_authenticated // session verification method
USER_SERVICE_INTERNAL_ACCESS_TOKEN_HEADER_KEY = internal_access_token // user service's internal access token header key
USER_SERVICE_INTERNAL_ACCESS_TOKEN_HEADER_KEY = internal_access_token // user service's internal access token header key


ENABLE_CATEGORY_KAFKA_EVENTS=true // kafka category event service
KAFKA_CATEGORY_TOPIC=category-updates // Kafka topic to listen category account events
340 changes: 277 additions & 63 deletions controllers/v1/library/categories.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,27 +71,52 @@ module.exports = class LibraryCategories extends Abstract {
* @param {Object} req - requested data
* @returns {Array} Library Categories project.
*/

async projects(req) {
return new Promise(async (resolve, reject) => {
try {
const libraryProjects = await libraryCategoriesHelper.projects(
req.params._id ? req.params._id : '',
req.pageSize,
req.pageNo,
req.searchText,
req.query.sort,
req.userDetails
)
try {
// Support both single and multiple category IDs
let categoryIds = []

// Method 1: Single ID from path parameter (GET /categories/:id/projects)
if (req.params._id) {
categoryIds = [req.params._id]
}
// Method 2: Comma-separated IDs from query string (GET /categories/projects?ids=id1,id2,id3)
else if (req.query.ids) {
categoryIds = req.query.ids
.split(',')
.map((id) => id.trim())
.filter((id) => id)
}

return resolve({
message: libraryProjects.message,
result: libraryProjects.data,
})
} catch (error) {
return reject(error)
if (!categoryIds || categoryIds.length === 0) {
throw {
status: HTTP_STATUS_CODE.bad_request.status,
message:
'categoryIds required - provide as path param, query string (comma-separated), or request body array',
}
}
})

const libraryProjects = await libraryCategoriesHelper.projects(
Comment thread
Sachintechjoomla marked this conversation as resolved.
categoryIds,
req.pageSize,
req.pageNo,
req.searchText,
req.query.sort,
req.userDetails
)

return {
success: true,
message: libraryProjects.message,
result: libraryProjects.data,
}
} catch (error) {
return {
status: error.status || HTTP_STATUS_CODE.internal_server_error.status,
message: error.message || HTTP_STATUS_CODE.internal_server_error.message,
errorObject: error,
}
}
}

/**
Expand All @@ -115,23 +140,41 @@ module.exports = class LibraryCategories extends Abstract {
* @returns {Object} Library project category details .
*/

/**
* @api {post} /project/v1/library/categories/create
* @apiVersion 1.0.0
* @apiName create
* @apiGroup LibraryCategories
* @apiHeader {String} x-auth-token Authenticity token
* @apiUse successBody
* @apiUse errorBody
*/
async create(req) {
return new Promise(async (resolve, reject) => {
try {
const libraryProjectcategory = await libraryCategoriesHelper.create(
req.body,
req.files,
req.userDetails
)

return resolve({
message: libraryProjectcategory.message,
result: libraryProjectcategory.data,
})
} catch (error) {
return reject(error)
try {
const result = await libraryCategoriesHelper.create(
req.body,
req.files,
req.userDetails
)
if (result.success) {
return {
success: true,
message: result.message,
result: result.data,
}
} else {
throw {
message: result.message,
status: result.status || HTTP_STATUS_CODE.bad_request.status,
}
}
} catch (error) {
return {
status: error.status || HTTP_STATUS_CODE.internal_server_error.status,
message: error.message || HTTP_STATUS_CODE.internal_server_error.message,
errorObject: error,
}
})
}
}

/**
Expand All @@ -155,27 +198,36 @@ module.exports = class LibraryCategories extends Abstract {
* @returns {Array} Library Categories project.
*/

/**
* @api {post} /project/v1/library/categories/update/:id
* @apiVersion 1.0.0
* @apiName update
* @apiGroup LibraryCategories
* @apiHeader {String} x-auth-token Authenticity token
* @apiUse successBody
* @apiUse errorBody
*/
async update(req) {
return new Promise(async (resolve, reject) => {
try {
const findQuery = {
_id: req.params._id,
try {
const result = await libraryCategoriesHelper.update(req)
if (result.success) {
return {
success: true,
message: result.message,
}
const libraryProjectcategory = await libraryCategoriesHelper.update(
findQuery,
req.body,
req.files,
req.userDetails
)

return resolve({
message: libraryProjectcategory.message,
result: libraryProjectcategory.data,
})
} catch (error) {
return reject(error)
} else {
throw {
message: result.message,
status: result.status || HTTP_STATUS_CODE.bad_request.status,
}
}
} catch (error) {
return {
status: error.status || HTTP_STATUS_CODE.internal_server_error.status,
message: error.message || HTTP_STATUS_CODE.internal_server_error.message,
errorObject: error,
}
})
}
}

/**
Expand Down Expand Up @@ -237,21 +289,183 @@ module.exports = class LibraryCategories extends Abstract {
* @returns {Array} Library categories.
*/

/**
* @api {get} /project/v1/library/categories/list
* @apiVersion 1.0.0
* @apiName list
* @apiGroup LibraryCategories
* @apiHeader {String} x-auth-token Authenticity token
* @apiUse successBody
* @apiUse errorBody
*/
async list(req) {
return new Promise(async (resolve, reject) => {
try {
let projectCategories = await libraryCategoriesHelper.list(req)
try {
const result = await libraryCategoriesHelper.list(req)
return {
success: true,
message: result.message,
result: result.data,
}
} catch (error) {
return {
status: error.status || HTTP_STATUS_CODE.internal_server_error.status,
message: error.message || HTTP_STATUS_CODE.internal_server_error.message,
errorObject: error,
}
}
}

/**
* @api {get} /project/v1/library/categories/:id/hierarchy
* @apiVersion 1.0.0
* @apiName categoryHierarchy
* @apiGroup LibraryCategories
* @apiHeader {String} x-auth-token Authenticity token
* @apiUse successBody
* @apiUse errorBody
*/
async hierarchy(req) {
try {
const result = await libraryCategoriesHelper.getCategoryHierarchy(req)
return {
success: true,
message: result.message,
result: result.data,
}
} catch (error) {
return {
status: error.status || HTTP_STATUS_CODE.internal_server_error.status,
message: error.message || HTTP_STATUS_CODE.internal_server_error.message,
errorObject: error,
}
}
}

projectCategories.result = projectCategories.data
/**
* @api {patch} /project/v1/library/categories/leaves
* @apiVersion 1.0.0
* @apiName leaves
* @apiGroup LibraryCategories
* @apiHeader {String} x-auth-token Authenticity token
* @apiUse successBody
* @apiUse errorBody
*/
async leaves(req) {
Comment thread
Sachintechjoomla marked this conversation as resolved.
try {
const result = await libraryCategoriesHelper.getLeaves(req)
return {
success: true,
message: result.message,
result: result.data,
}
} catch (error) {
return {
status: error.status || HTTP_STATUS_CODE.internal_server_error.status,
message: error.message || HTTP_STATUS_CODE.internal_server_error.message,
errorObject: error,
}
}
}

return resolve(projectCategories)
} catch (error) {
return reject({
status: error.status || HTTP_STATUS_CODE.internal_server_error.status,
message: error.message || HTTP_STATUS_CODE.internal_server_error.message,
errorObject: error,
})
/**
* @api {post} /project/v1/library/categories/bulk
* @apiVersion 1.0.0
* @apiName bulk
* @apiGroup LibraryCategories
* @apiHeader {String} x-auth-token Authenticity token
* @apiUse successBody
* @apiUse errorBody
*/
async bulk(req) {
try {
const categories = req.body.categories || []

if (!Array.isArray(categories) || categories.length === 0) {
throw {
status: HTTP_STATUS_CODE.bad_request.status,
message: 'categories required - provide a non-empty array in request body',
}
}

const result = await libraryCategoriesHelper.bulkCreate(
categories,
req.userDetails
)
return {
success: true,
message: result.message,
result: result.data,
}
} catch (error) {
return {
status: error.status || HTTP_STATUS_CODE.internal_server_error.status,
message: error.message || HTTP_STATUS_CODE.internal_server_error.message,
errorObject: error,
}
}
}

/**
* @api {delete} /project/v1/library/categories/delete/:id
* @apiVersion 1.0.0
* @apiName delete
* @apiGroup LibraryCategories
* @apiHeader {String} x-auth-token Authenticity token
* @apiUse successBody
* @apiUse errorBody
*/
async delete(req) {
try {
const result = await libraryCategoriesHelper.delete(req)
if (result.success) {
return {
success: true,
message: result.message,
result: result.data,
}
} else {
throw {
message: result.message,
status: result.status || HTTP_STATUS_CODE.bad_request.status,
}
}
} catch (error) {
return {
status: error.status || HTTP_STATUS_CODE.internal_server_error.status,
message: error.message || HTTP_STATUS_CODE.internal_server_error.message,
errorObject: error,
}
}
}

/**
* @api {get} /project/v1/library/categories/:id
* @apiVersion 1.0.0
* @apiName details
* @apiGroup LibraryCategories
* @apiHeader {String} x-auth-token Authenticity token
* @apiUse successBody
* @apiUse errorBody
*/
async details(req) {
try {
const categoryId = req.params._id

const result = await libraryCategoriesHelper.details(
categoryId,
req.userDetails
)
return {
success: true,
message: result.message,
result: result.data,
}
} catch (error) {
return {
status: error.status || HTTP_STATUS_CODE.internal_server_error.status,
message: error.message || HTTP_STATUS_CODE.internal_server_error.message,
errorObject: error,
}
})
}
}
}
Loading