From 9d2e145d4f3e44ec43b63ad4bdcb9170699b0c5d Mon Sep 17 00:00:00 2001 From: Nivetharamesh Date: Fri, 31 Oct 2025 15:50:01 +0530 Subject: [PATCH] group add and ungroup API completed --- src/api-server/app.ts | 2 + src/api-server/controller/groupController.ts | 140 ++++++--- src/api-server/routes/groupRoutes.ts | 8 + src/shared/model/collectionModel.ts | 3 + src/shared/model/groupModel.ts | 2 + src/shared/services/collectionService.ts | 79 +++-- src/shared/services/groupService.ts | 288 +++++++++++-------- 7 files changed, 319 insertions(+), 203 deletions(-) create mode 100644 src/api-server/routes/groupRoutes.ts diff --git a/src/api-server/app.ts b/src/api-server/app.ts index 8537860..648110d 100644 --- a/src/api-server/app.ts +++ b/src/api-server/app.ts @@ -7,6 +7,7 @@ import edgeRoutes from "./routes/edgeRoutes"; import authRoutes from "./routes/authRoutes"; import homeRoutes from "./routes/homeRoutes"; import dummyRoutes from "./routes/dummyRoutes"; +import groupRoutes from "./routes/groupRoutes"; dotenv.config(); const app = express(); @@ -22,5 +23,6 @@ app.use("/api/v1", collectionNodeRoutes); app.use("/api/v1", edgeRoutes); app.use("/api/v1", homeRoutes); app.use("/api/v1", dummyRoutes); +app.use("/api/v1", groupRoutes); export default app; diff --git a/src/api-server/controller/groupController.ts b/src/api-server/controller/groupController.ts index d669229..cd3eb15 100644 --- a/src/api-server/controller/groupController.ts +++ b/src/api-server/controller/groupController.ts @@ -1,6 +1,6 @@ import { Request, Response } from "express"; import { AuthenticatedRequest } from "../../shared/utils/token"; -import { groupcreation } from "../../shared/services/groupService"; +import { groupcreationService, unGrpService } from "../../shared/services/groupService"; export const addGroupController = async ( req: AuthenticatedRequest, @@ -12,7 +12,7 @@ export const addGroupController = async ( const missing = Object.entries({ organization, projectId, - position, + // position, userId, groupName, type, @@ -37,43 +37,107 @@ export const addGroupController = async ( collections, userId: userId as string, }; - const result = await groupcreation(data); - console.log("result:groupcretate ", result); + const result = await groupcreationService(data); + console.log('result:grp add ', result); - switch (result.status) { - case "User not found": - res.status(404).json({ message: "User not found" }); - break; - case "project not found": - res.status(404).json({ - message: "project not found", - }); - break; - case "Collections already exist in this group": - res.status(200).json({ - message: "Collections already exist in this group", - }); - break; - case "Collections added to existing group": - res.status(200).json({ - message: "Collections added to existing group", - }); - break; - case "Success": - res.status(200).json({ - message: "Group created successfully", - groupId: result.data, - }); - break; - case "Validation Error": - res.status(400).json({ message: result.data || "Validation Error" }); - break; - default: - res.status(500).json({ - message: "Internal server error", - }); - break; - } + // switch (result.status) { + // case "User not found": + // res.status(404).json({ message: "User not found" }); + // break; + // case "project not found": + // res.status(404).json({ + // message: "project not found", + // }); + // break; + // case "CollectionId already exist in this group": + // res.status(200).json({ + // message: "CollectionId already exist in this group", + // }); + // break; + // case "Collections added to existing group": + // res.status(200).json({ + // message: "Collections added to existing group", + // }); + // break; + // case "Group already exists": + // res.status(200).json({ + // message: "Group already exists", + // }); + // break; + // case "Success": + // res.status(200).json({ + // message: "Group created successfully", + // groupId: result.data, + // }); + // break; + // case "Validation Error": + // res.status(400).json({ message: result.data || "Validation Error" }); + // break; + // default: + // res.status(500).json({ + // message: "Internal server error", + // }); + // break; + // } + + if ( + result.status === "User not found" || + result.status === "Project not found" + ) + res.status(404).json({ message: result.status }); + else if (result.status === "Error") + res.status(500).json({ message: result.data }); + else res.status(200).json({ message: result.status, groupId: result.data }); + } catch (error) { + res.status(500).json({ + message: "Unknown error", + }); + } +}; + +export const unGroupController = async ( + req: AuthenticatedRequest, + res: Response +): Promise => { + try { + const { organization, userId } = req.user || {}; + const { projectId, position, groupId, collections } = req.body; + const missing = Object.entries({ + organization, + projectId, + // position, + userId, + groupId, + }) + .filter(([_, v]) => !v) + .map(([k]) => k); + + if (missing.length) { + res.status(400).json({ + message: `Missing field${missing.length > 1 ? "s" : ""}: ${missing.join( + ", " + )}`, + }); + return; + } + const data = { + organization: organization as string, + projectId, + position, + groupId, + collections, + userId: userId as string, + }; + const result = await unGrpService(data); + console.log('result:ungrp ', result); + if ( + result.status === "User not found" || + result.status === "Project not found" + ) + res.status(404).json({ message: result.status }); + else if (result.status === "Error") + res.status(500).json({ message: result.data }); + else res.status(200).json({ message: result.status, groupId: result.data }); } catch (error) { res.status(500).json({ message: "Unknown error", diff --git a/src/api-server/routes/groupRoutes.ts b/src/api-server/routes/groupRoutes.ts new file mode 100644 index 0000000..29a0e91 --- /dev/null +++ b/src/api-server/routes/groupRoutes.ts @@ -0,0 +1,8 @@ +import express from "express" +import { tokenValidator } from "../../shared/utils/token" +import { addGroupController, unGroupController } from "../controller/groupController" +const groupRoutes = express() +//group create +groupRoutes.post("/groupadd",tokenValidator,addGroupController) +groupRoutes.post("/ungroup",tokenValidator,unGroupController) +export default groupRoutes \ No newline at end of file diff --git a/src/shared/model/collectionModel.ts b/src/shared/model/collectionModel.ts index 55423ab..edf2b5b 100644 --- a/src/shared/model/collectionModel.ts +++ b/src/shared/model/collectionModel.ts @@ -1,6 +1,7 @@ import { Schema, Document } from "mongoose"; import MainModel from "../connection/connection"; import { IProject } from "./projectmodel"; +import { IgroupModel } from "./groupModel"; type IattributeTypes = | "string" | "any" @@ -35,6 +36,7 @@ export interface ICollectionNode extends Document { parentCollectionNodeId: ICollectionNode["_id"]; isSubCollection: boolean; attributeparentId: ICollectionNode["_id"]; + groupParentId: IgroupModel["_id"]; collectionName: string; attributes: IAttributes[]; backgroundColor: BackgroundColor; @@ -114,6 +116,7 @@ const collectionSchema: Schema = new Schema( projectId: { type: Schema.Types.ObjectId, ref: "Project" }, parentCollectionNodeId: { type: Schema.Types.ObjectId, ref: "Collection" }, attributeparentId: { type: Schema.Types.ObjectId, ref: "Collection" }, + groupParentId: { type: Schema.Types.ObjectId, ref: "Collection" }, collectionName: { type: String }, type: { type: String, enum: ["collectionNode", "objectNode"] }, backgroundColor: { type: backgroundColorSchema }, diff --git a/src/shared/model/groupModel.ts b/src/shared/model/groupModel.ts index ce03a9e..374ac27 100644 --- a/src/shared/model/groupModel.ts +++ b/src/shared/model/groupModel.ts @@ -9,6 +9,7 @@ export interface IgroupModel extends Document { position: { x: number; y: number; + zoom: number; }; projectId: IProject["_id"]; collections: ICollectionNode["_id"][]; @@ -22,6 +23,7 @@ const GroupSchema = new Schema( position: { x: { type: Number }, y: { type: Number }, + zoom: { type: Number }, }, projectId: { type: Schema.Types.ObjectId, ref: "Project", required: true }, collections: [{ type: Schema.Types.ObjectId, ref: "Collection" }], diff --git a/src/shared/services/collectionService.ts b/src/shared/services/collectionService.ts index 0e0381e..e79e279 100644 --- a/src/shared/services/collectionService.ts +++ b/src/shared/services/collectionService.ts @@ -5,6 +5,7 @@ import edgeModel from "../model/edgeModel"; import userModel from "../model/userModel"; import { modelNodeFile, updateFilename } from "./fileFunctionalityService"; import { IattributeTypes } from "../model/dummycollectionmodel"; +import groupModel from "../model/groupModel"; interface Iresponse { status: string; data?: any; @@ -538,7 +539,7 @@ export const GetNodesInProject = async ( let collectionNodes = await collectionsModel(organization) .find({ projectId: projectId, isArchive: false }) .select( - "collectionName attributes position _id type parentCollectionNodeId attributeparentId" + "collectionName attributes position _id type parentCollectionNodeId attributeparentId groupParentId" ); if (!collectionNodes) return { status: "No collection Nodes present", data: [] }; @@ -580,11 +581,37 @@ export const GetNodesInProject = async ( }; }) ); + let formattedGroups=[]; + const groupDatas = await groupModel(organization).find({ + isArchive: false, + projectId:projectId, + }); + // let formattedGroups + for (const grpdata of groupDatas) { + const collectionsDatas = await collectionsModel(organization).find({ + isArchive: false, + projectId, + groupParentId: grpdata._id, + }); + formattedGroups.push({ + groupId: grpdata._id, + type: grpdata.type, + position: grpdata.position, + data: { + label: grpdata.groupName, + childrenCount: collectionsDatas.length, + }, + }); + } + // return formattedGroups const formattedCollections = collectionNodes.map((collection: any) => { + // console.log("collection: ", collection); + // console.log("collection.groupParentId: ", collection.groupParentId); const baseData = { id: collection._id, position: collection.position, type: collection.type, + parentGroupNode: `group - ${collection.groupParentId}`|| " ", data: { collectionName: collection.collectionName, collectionData: collection.attributes @@ -622,11 +649,13 @@ export const GetNodesInProject = async ( } return baseData; }); - console.log("filteredEdges: ", filteredEdges); + // console.log("filteredEdges: ", filteredEdges); const finalResult = { nodes: formattedCollections, edges: filteredEdges, + groups: formattedGroups, }; + // console.log("finalResult: ", finalResult); return { status: "Success", data: finalResult }; } } @@ -779,9 +808,8 @@ export const UpdateAttributes = async ( doc.markModified("attributes"); await doc.save(); } - } catch (err:any) { - return { status: "Validation Error", data: err.message }; - + } catch (err: any) { + return { status: "Validation Error", data: err.message }; } } } @@ -1131,13 +1159,6 @@ export const DuplicateAttributes = async ( newKey = `${attrKey}(${counter})`; console.log("newKey: ", newKey); } - - console.log("existingAttr.type: ", existingAttr.type); - // existingCollection.attributes.push({ - // key: newKey, - // type: existingAttr.type, - // isArchive: false, - // }); } else { return { status: "Attribute doesnot match" }; } @@ -1205,7 +1226,6 @@ export const GetcollectionLists = async ( } }; -// - working export const DelAttributes = async ( data: IAttributesDel ): Promise => { @@ -1289,7 +1309,7 @@ export const DelAttributes = async ( return { status: "An unexpected error occurred" }; } }; -// - working + export const addAttributes = async ( data: IcollectionAttributes ): Promise => { @@ -1298,21 +1318,18 @@ export const addAttributes = async ( console.log("data: ", data); try { - // 1️⃣ Validate User const existingUser = await userModel(organization).findOne({ _id: userId, isArchive: false, }); if (!existingUser) return { status: "User not found" }; - // 2️⃣ Validate Project const existingProject = await ProjectType(organization).findOne({ _id: projectId, isArchive: false, }); if (!existingProject) return { status: "Project not found" }; - // 3️⃣ Validate Collection const existingCollection = await collectionsModel(organization).findOne({ projectId, _id: collectionNodeId, @@ -1322,7 +1339,6 @@ export const addAttributes = async ( const existingAttributes = existingCollection.attributes || []; - // 4️⃣ Check for duplicates with isArchive: false for (const attr of attributes) { const duplicate = existingAttributes.find( (exAttr) => exAttr.key === attr.key && !exAttr.isArchive @@ -1333,10 +1349,8 @@ export const addAttributes = async ( } } - // 5️⃣ Merge new attributes const updatedAttributes = [...existingAttributes, ...attributes]; - // 6️⃣ Update collection const attributesAdded = await collectionsModel( organization ).findOneAndUpdate( @@ -1347,7 +1361,6 @@ export const addAttributes = async ( if (!attributesAdded) return { status: "Failed to add attributes" }; - // 7️⃣ Create subcollections for Object type attributes for (const attr of attributes) { if (attr.type === "Object") { const existingSubCollection = await collectionsModel( @@ -1376,34 +1389,10 @@ export const addAttributes = async ( await attributesAdded.save(); - // 8️⃣ Return newly added attribute IDs const newlyAddedAttributes = attributesAdded.attributes.filter((attr) => attributes.some((newAttr) => newAttr.key === attr.key) ); const addedFieldIds = newlyAddedAttributes.map((attr) => (attr as any)._id); - console.log("addedFieldIds: ", addedFieldIds); - // 9️⃣ 🧠 Update the model file dynamically - // try { - // const allAttributes = attributesAdded.attributes.reduce((acc, attr) => { - // acc[attr.key] = attr.type; - // return acc; - // }, {} as Record); - - // const fileData = { - // projectName: existingProject.projectName, - // models1: [ - // { - // name: existingCollection.collectionName, - // attributes: allAttributes, - // }, - // ], - // }; - - // const fileResponse = await modelNodeFile(fileData); - // console.log("✅ Model file updated:", fileResponse); - // } catch (fileErr) { - // console.error("⚠️ File backend update failed:", fileErr); - // } return { status: "Success", data: addedFieldIds, diff --git a/src/shared/services/groupService.ts b/src/shared/services/groupService.ts index 7934874..7639dd4 100644 --- a/src/shared/services/groupService.ts +++ b/src/shared/services/groupService.ts @@ -22,22 +22,21 @@ interface IgroupNode { position: { x: number; y: number; + zoom: number; }; } -interface IgroupNodeupdate { +interface IunGrp { projectId: string; + groupId: string; userId: string; - groupName: string; - type: string; organization: string; - position: { - x: number; - y: number; - }; + collections: IcollectionsNode[]; } -export const groupcreation = async (data: IgroupNode): Promise => { +export const groupcreationService = async ( + data: IgroupNode +): Promise => { const { organization, projectId, @@ -66,78 +65,105 @@ export const groupcreation = async (data: IgroupNode): Promise => { isArchive: false, groupName: groupName, }); - // if (existingGroup) return { status: "Group already exists" }; - // else { - // if (collections.length > 0) { - // const newcoln = collections; - // const updatedCollectionsMap = new Map(); + if (!existingGroup) { + const validCollectionIds: mongoose.Types.ObjectId[] = []; + const invalidIds: string[] = []; - // for (const coln of collections) { - // updatedCollectionsMap.set(coln, 0); - // } + for (const collect of collections) { + const aliveCollection = await collectionsModel(organization).findOne({ + _id: collect.collectionNodeId, + isArchive: false, + }); + const existingGroupNode = await groupModel(organization).find({ + isArchive: false, + projectId, + collections: collect.collectionNodeId, + }); + console.log("existingGroupNode: ", existingGroupNode); + if (existingGroupNode.length > 0) + return { status: "collectionId already added to other Group" }; + if (aliveCollection && existingGroupNode.length === 0) { + validCollectionIds.push( + new mongoose.Types.ObjectId(collect.collectionNodeId) + ); + } else { + invalidIds.push(collect.collectionNodeId); + } + } - // for (const coln of collections) { - // if (!updatedCollectionsMap.has(coln)) { - // updatedCollectionsMap.set(coln); - // } - // } - - // const updatedCollections = Array.from(updatedCollectionsMap.values()); - // const newGroup = await groupModel(organization).create({ - // groupName, - // projectId, - // position, - // createdBy: userId, - // collections: updatedCollections, - // }); - // // } - // if (newGroup) - // return { - // status: "NewGroup Created Successfully", - // data: newGroup._id, - // }; - // else { - // return { - // status: "Creation Unsuccessfull", - // }; - // } - // } - // } - // } - - const collectionIds = collections.map( - (c) => new mongoose.Types.ObjectId(c.collectionNodeId) - ); - - if (existingGroup) { - const existingIds = existingGroup.collections.map((id) => - (id as any).toString() - ); - const newIds = collectionIds.filter( - (id) => !existingIds.includes(id.toString()) - ); - - if (newIds.length === 0) - return { status: "Collections already exist in this group" }; - - existingGroup.collections.push(...newIds); - await existingGroup.save(); - return { - status: "Collections added to existing group", - data: existingGroup._id, - }; - } else { const newGroup = await groupModel(organization).create({ groupName, type, position, projectId, createdBy: userId, - collections: [ - ...new Set(collectionIds.map((id) => id.toString())), - ].map((id) => new mongoose.Types.ObjectId(id)), + collections: validCollectionIds, }); - return { status: "Success", data: newGroup._id }; + if (validCollectionIds.length > 0) { + await collectionsModel(organization).updateMany( + { _id: { $in: validCollectionIds } }, + { $set: { groupParentId: newGroup._id } } + ); + } + if (invalidIds.length > 0) { + return { + // status: "Partial Success", + status: `Group created. Invalid IDs: ${invalidIds.join(", ")}`, + data: newGroup._id, + }; + } + + return { status: "Group created successfully", data: newGroup._id }; + } else { + const validCollectionIds: mongoose.Types.ObjectId[] = []; + const invalidIds: string[] = []; + + for (const collect of collections) { + console.log("collections: ", collections); + const aliveCollection = await collectionsModel(organization).findOne({ + _id: collect.collectionNodeId, + isArchive: false, + }); + const existingGroupNode = await groupModel(organization).find({ + isArchive: false, + projectId, + collections: collect.collectionNodeId, + }); + if (existingGroupNode.length > 0) + return { status: "collectionId already added to other Group" }; + if (aliveCollection && existingGroupNode.length === 0) { + validCollectionIds.push( + new mongoose.Types.ObjectId(collect.collectionNodeId) + ); + } else { + invalidIds.push(collect.collectionNodeId); + } + } + const existingIds = existingGroup.collections.map((id: any) => + id.toString() + ); + const newIds = validCollectionIds.filter( + (id) => !existingIds.includes(id.toString()) + ); + + if (newIds.length > 0) { + existingGroup.collections.push(...newIds); + await existingGroup.save(); + await collectionsModel(organization).updateMany( + { _id: { $in: newIds } }, + { $set: { groupParentId: existingGroup._id } } + ); + } + + if (newIds.length === 0 && invalidIds.length === 0) { + return { status: "All Collection IDs already exist in this group" }; + } + + let msg = ""; + if (newIds.length) msg += `Added ${newIds.length} new collections. `; + if (invalidIds.length) msg += `Invalid IDs: ${invalidIds.join(", ")}.`; + + return { status: msg.trim(), data: existingGroup._id }; } } } catch (error: unknown) { @@ -151,52 +177,74 @@ export const groupcreation = async (data: IgroupNode): Promise => { } }; -// export const addNodesToGroup = async (data: IgroupNode): Promise => { -// const { organization, projectId, position, userId, groupId, groups } = data; -// try { -// const ExistingUser = await userModel(organization).findOne({ -// _id: userId, -// isArchive: false, -// }); -// if (!ExistingUser) return { status: "User not found" }; -// const existingProject = await ProjectType(organization).findOne({ -// _id: projectId, -// isArchive: false, -// }); -// if (!existingProject) { -// return { status: "project not found" }; -// } else { -// const existingGroup = await groupModel(organization).findOne({ -// projectId: projectId, -// isArchive: false, -// groupId: groupId, -// }); -// if (existingGroup) { -// const existingGroup = await groupModel(organization).findOneAndUpdate( -// { -// projectId: projectId, -// isArchive: false, -// groupId: groupId, -// }, -// { -// groups: groups, -// }, -// { new: true } -// ); -// if (existingGroup) { -// return { status: "Group updated successfully" }; -// } else { -// return { status: "Group update Unsuccessfully" }; -// } -// } -// } -// } catch (error: unknown) { -// if (error instanceof mongoose.Error.ValidationError) { -// return { status: "Validation Error", data: error.message }; -// } else if (error instanceof Error) { -// return { status: error.message }; -// } else { -// return { status: "An unexpected error occurred" }; -// } -// } -// }; +export const unGrpService = async (data: IunGrp): Promise => { + const { organization, projectId, userId, groupId, collections } = data; + try { + const ExistingUser = await userModel(organization).findOne({ + _id: userId, + isArchive: false, + }); + + if (!ExistingUser) return { status: "User not found" }; + const existingProject = await ProjectType(organization).findOne({ + _id: projectId, + isArchive: false, + }); + if (!existingProject) { + return { status: "project not found" }; + } else { + const existingGroup = await groupModel(organization).findOne({ + projectId: projectId, + isArchive: false, + _id: groupId, + }); + if (!existingGroup) return { status: "Group not found for this Id" }; + + if (existingGroup.collections.length === 0) { + return { status: "No collections found to ungroup" }; + } + + if (collections.length > 0) { + for (const coln of collections) { + const index1 = ( + existingGroup.collections as mongoose.Types.ObjectId[] + ).findIndex( + (c: object) => c.toString() === coln.collectionNodeId.toString() + ); + + if (index1 !== -1) { + existingGroup.collections.splice(index1, 1); + await existingGroup.save(); + + const deleteCollection = await collectionsModel( + organization + ).findOne({ + _id: coln.collectionNodeId, + projectId, + isArchive: false, + }); + + if (deleteCollection) { + deleteCollection.groupParentId = undefined; + await deleteCollection.save(); + } + } else { + return { status: "collectionId mismatch" }; + } + } + + return { status: "Success" }; + } + + return { status: "No collections provided for ungrouping" }; + } + } catch (error: unknown) { + if (error instanceof mongoose.Error.ValidationError) { + return { status: "Validation Error", data: error.message }; + } else if (error instanceof Error) { + return { status: error.message }; + } else { + return { status: "An unexpected error occurred" }; + } + } +};