diff --git a/src/api-server/controller/collectionNodeController.ts b/src/api-server/controller/collectionNodeController.ts index 9d2bedc..3c4d1b2 100644 --- a/src/api-server/controller/collectionNodeController.ts +++ b/src/api-server/controller/collectionNodeController.ts @@ -151,7 +151,7 @@ export const updateCollectionController = async ( position, }; const result = await updatecollection(data); - console.log('result: ', result); + switch (result.status) { case "User not found": res.status(404).json({ message: "User not found" }); diff --git a/src/api-server/controller/edgeController.ts b/src/api-server/controller/edgeController.ts index 12fd5ae..42448e2 100644 --- a/src/api-server/controller/edgeController.ts +++ b/src/api-server/controller/edgeController.ts @@ -41,7 +41,7 @@ export const edgeCreationController = async ( userId: userId as string, }; const result = await edgecreation(data); - console.log('result: ', result); + switch (result.status) { case "User not found": @@ -117,7 +117,7 @@ export const allEdgesController = async ( userId: userId as string, }; const result = await Alledges(data); - console.log('result:all edge ', result); + switch (result.status) { case "User not found": diff --git a/src/api-server/controller/projectController.ts b/src/api-server/controller/projectController.ts index 3c82d69..7f2a0aa 100644 --- a/src/api-server/controller/projectController.ts +++ b/src/api-server/controller/projectController.ts @@ -64,27 +64,27 @@ export const projectCreationController = async ( architecture, }; const result = await projectCreationService(data); - if (result.status === "Success") { - const fetchResult = await folderProject( - subBkd, - projectName, - language, - apiProtocol, - application, - architecture - ); - if (fetchResult) { - res.status(200).json({ - message: "Project creation unsuccessfull", - projectId: result.data, - }); - // return { status: "Success", data: fetchResult.data }; - } else { - res.status(500).json({ - message: "error", - }); - } - } + // if (result.status === "Success") { + // const fetchResult = await folderProject( + // subBkd, + // projectName, + // language, + // apiProtocol, + // application, + // architecture + // ); + // if (fetchResult) { + // res.status(200).json({ + // message: "Project creation unsuccessfull", + // projectId: result.data, + // }); + // // return { status: "Success", data: fetchResult.data }; + // } else { + // res.status(500).json({ + // message: "error", + // }); + // } + // } switch (result.status) { case "Project Already Exists": res.status(403).json({ @@ -270,7 +270,7 @@ export const accessAproject = async ( userId: userId as string, projectId, }); - console.log('result.data: ', result); + switch (result.status) { case "No project found": res.status(200).json({}); diff --git a/src/shared/model/collectionModel.ts b/src/shared/model/collectionModel.ts index edf2b5b..cf420b3 100644 --- a/src/shared/model/collectionModel.ts +++ b/src/shared/model/collectionModel.ts @@ -22,6 +22,7 @@ interface IAttributes { isArchive: boolean; isIdentifier: boolean; primary?: boolean; + keyType?: string; key: string; type: IattributeTypes; refKey?: object; @@ -60,7 +61,7 @@ const attributeSchema = new Schema({ key: { type: String, required: true }, type: { type: String, - required: true, + // required: true, enum: [ "string", "number", @@ -82,7 +83,8 @@ const attributeSchema = new Schema({ refKey: { type: Object }, default: { type: Schema.Types.Mixed }, unique: { type: Boolean }, - primary: { type: Boolean }, + // primary: { type: Boolean }, + keyType: { type: String }, index: { type: Boolean }, isArchive: { type: Boolean, default: false }, isIdentifier: { type: Boolean, default: false }, diff --git a/src/shared/services/collectionService.ts b/src/shared/services/collectionService.ts index e79e279..fcd15b9 100644 --- a/src/shared/services/collectionService.ts +++ b/src/shared/services/collectionService.ts @@ -165,7 +165,7 @@ export const Nodecreation = async ( }); if (existingCollection) return { status: "CollectionName already exists" }; - let attributes = [{ key: "auto_id", type: "string" }]; + let attributes = [{ key: "auto_id", type: "string" ,keyType:"primary"}]; const newAttributes = attributes; const updatedAttributesMap = new Map(); @@ -190,10 +190,16 @@ export const Nodecreation = async ( const models = [newCollectionnode.collectionName]; const finalResult = { collectionNodeId: newCollectionnode._id, - fieldId: (newCollectionnode.attributes[0] as any)._id, - models, - }; - console.log("finalResult: ", finalResult); + // fieldId: (newCollectionnode.attributes[0] as any)._id, + // models, + type:newCollectionnode.type, + position:newCollectionnode.position, + data:{ + collectionName:newCollectionnode.collectionName, + collectionData:newCollectionnode.attributes, + } + }; + // console.log("finalResult: ", finalResult); const models1 = [ { name: newCollectionnode.collectionName, @@ -210,12 +216,12 @@ export const Nodecreation = async ( projectName: existingProject.projectName, // 👈 take from DB models1, }; - console.log("fileData: ", fileData); + // console.log("fileData: ", fileData); try { const fileResponse = await modelNodeFile(fileData); - console.log("📦 File backend response:", fileResponse); + // console.log("📦 File backend response:", fileResponse); } catch (err) { - console.error("⚠️ File backend error:", err); + // console.error("⚠️ File backend error:", err); } return { status: "Success", data: finalResult }; } @@ -251,7 +257,8 @@ export const updatecollection = async ( collectionName, userId, } = data; - console.log("data:node ", data); + console.log('data: nodeupdate', data); + try { const ExistingUser = await userModel(organization).findOne({ _id: userId, @@ -276,7 +283,6 @@ export const updatecollection = async ( } function updateModelName(oldName: string, newBaseName: string): string { - console.log("newBaseName: ", newBaseName); const [prefix, ...suffixParts] = oldName.split("_"); if (suffixParts.length > 0) { @@ -287,44 +293,119 @@ export const updatecollection = async ( return newBaseName; } - const newBaseName = collectionName as string; - const referencedEdges = await collectionsModel(organization).find({ - isArchive: false, - projectId, - attributes: { - $elemMatch: { - "refKey.collection_id": new mongoose.Types.ObjectId( - collectionNodeId - ), - }, - }, - }); + // const referencedEdges = await collectionsModel(organization).find({ + // isArchive: false, + // projectId, + // attributes: { + // $elemMatch: { + // "refKey.collection_id": new mongoose.Types.ObjectId( + // collectionNodeId + // ), + // }, + // }, + // }); - console.log("referencedEdges: ", referencedEdges); - if (referencedEdges.length) { - for (const doc of referencedEdges) { - let updated = false; + // console.log('referencedEdges: ', referencedEdges); + // if (referencedEdges.length) { + // for (const doc of referencedEdges) { + // let updated = false; - for (const attr of doc.attributes) { - const refKey = attr.refKey as any; - if ( - refKey?.collection_id?.toString() === collectionNodeId.toString() - ) { - const oldName = attr.key; - const newKey = updateModelName(oldName, newBaseName); - console.log("newKey: ", newKey); - attr.key = newKey; - updated = true; - } - } + // for (const attr of doc.attributes) { + // const refKey = attr.refKey as any; + // if ( + // refKey?.collection_id?.toString() === collectionNodeId.toString() + // ) { + // const newBaseName = collectionName as string; + // const oldName = attr.key; + // const newKey = updateModelName(oldName, newBaseName); + // attr.key = newKey; + // updated = true; + // } + // } - if (updated) { - await doc.save(); - } - } + // if (updated) { + // await doc.save(); + // } + // } + // } +async function updateReferencedKeysRecursively( + organization: string, + projectId: string, + collectionNodeId: any, + collectionName: string, + visited = new Set() +): Promise { + if (visited.has(collectionNodeId)) return; + visited.add(collectionNodeId); + + const referencedEdges = await collectionsModel(organization).find({ + isArchive: false, + projectId, + $or: [ + { "attributes.refKey.collection_id": new mongoose.Types.ObjectId(collectionNodeId) }, + { "attributes.refKey.collection_id": collectionNodeId }, + ], +}); + + + console.log('referencedEdges: ', referencedEdges); + console.log( + `🔍 Found ${referencedEdges.length} referencing collections for '${collectionName}'` + ); + + for (const doc of referencedEdges) { + let updated = false; + + for (const attr of doc.attributes) { + const refKey = attr.refKey as any; + + if (refKey?.collection_id?.toString() === collectionNodeId.toString()) { + const oldName = attr.key; + const newBaseName = collectionName; + const newKey = updateModelName(oldName, newBaseName); + + attr.key = newKey; + updated = true; + + console.log( + `🔁 Updated key in '${doc.collectionName}': ${oldName} → ${newKey} (ref to '${collectionName}')` + ); } + } + + if (updated) { + await doc.save(); + console.log(`✅ Saved changes in '${doc.collectionName}'`); + } + + // 🔁 Recursive update if the collection itself has a valid name + if (doc.collectionName) { + await updateReferencedKeysRecursively( + organization, + projectId, + doc._id, + doc.collectionName, + visited + ); + } else { + console.warn(`⚠️ Skipped recursion for ${doc._id} (no collectionName)`); + } + } +} +const currentCollectionName = + collectionName || existingCollection.collectionName; + +await updateReferencedKeysRecursively( + organization, + projectId, + collectionNodeId, + currentCollectionName +); + + const oldName = existingCollection.collectionName; const newName = collectionName; + const collectionNameupdate = await collectionsModel( organization ).findOneAndUpdate( @@ -348,25 +429,25 @@ export const updatecollection = async ( }, { new: true } ); - // console.log('collectionNameupdate: ', collectionNameupdate); +// console.log('collectionNameupdate: ', collectionNameupdate); if (collectionNameupdate) { // ✅ If name changed, trigger file rename - if (collectionNameupdate && oldName !== newName) { - console.log(`Renaming model file from ${oldName} → ${newName}`); + // if (collectionNameupdate && oldName !== newName) { + // console.log(`Renaming model file from ${oldName} → ${newName}`); - await updateFilename({ - projectName: existingProject.projectName, + // await updateFilename({ + // projectName: existingProject.projectName, - oldModelName: existingCollection.collectionName, - newModelName: collectionName as string, - }); - } + // oldModelName: existingCollection.collectionName, + // newModelName: collectionName as string, + // }); + // } return { status: "Success", data: { collectionNameupdate: collectionNameupdate, - updateFileds: referencedEdges, + // updateFileds: referencedEdges, }, }; } else { @@ -699,6 +780,7 @@ export const UpdateAttributes = async ( }); if (!existingCollection) return { status: "Collection not found" }; + let referencedEdges; for (const attr of attributes) { const fieldId = attr.fieldId; if (attr.type === "Object") { @@ -710,7 +792,7 @@ export const UpdateAttributes = async ( attributeparentId: fieldId, isArchive: false, }); - console.log("existingsubcollection: ", existingsubcollection); + if (!existingsubcollection) { const fieldnamefind = await collectionsModel(organization).findOne( { @@ -742,70 +824,100 @@ export const UpdateAttributes = async ( }; } } - const referencedEdges = await collectionsModel(organization).find({ - isArchive: false, - projectId, - attributes: { - $elemMatch: { - "refKey.collection_id": new mongoose.Types.ObjectId( - collectionNodeId - ), - "refKey.fieldId": new mongoose.Types.ObjectId(fieldId), - }, - }, - }); - console.log("referencedEdges: ", referencedEdges); - if (referencedEdges.length) { - for (const doc of referencedEdges) { - let updated = false; + if (attr.type) { + referencedEdges = await collectionsModel(organization) + .find({ + isArchive: false, + projectId, + attributes: { + $elemMatch: { + "refKey.collection_id": new mongoose.Types.ObjectId( + collectionNodeId + ), + "refKey.fieldId": new mongoose.Types.ObjectId(fieldId), + }, + }, + }) + .select("attributes projectId collectionName type"); - // for (const oldattr of doc.attributes) { - // console.log("oldattr: ", oldattr); - // const refKey = oldattr.refKey as any; - // if ( - // refKey?.collection_id?.toString() === - // collectionNodeId.toString() && - // refKey?.fieldId?.toString() === fieldId.toString() - // ) { - // const oldType = oldattr.type; - // const newType = attr.type; + if (referencedEdges.length) { + for (const doc of referencedEdges) { + let updated = false; + try { + for (const oldattr of doc.attributes) { + const refKey = oldattr.refKey as any; + if ( + refKey?.collection_id?.toString() === + collectionNodeId.toString() && + refKey?.fieldId?.toString() === fieldId.toString() + ) { + const oldType = oldattr.type; + const newType = attr.type; + oldattr.type = newType as any; + updated = true; + } + } - // console.log("oldType:", oldType, "→ newType:", newType); + if (updated) { + doc.markModified("attributes"); + await doc.save(); + } + } catch (err: any) { + return { status: "Validation Error", data: err.message }; + } + } + } + } - // // ✅ Update directly - // oldattr.type = newType as any; + if (attr.key) { + function updateModelName( + oldName: string, + newBaseName: string + ): string { + const firstUnderscoreIndex = oldName.indexOf("_"); - // updated = true; - // } - // } + if (firstUnderscoreIndex === -1) { + return newBaseName; + } - // if (updated) { - // doc.markModified("attributes"); - // console.log("hi"); - // await doc.save(); - // console.log("hello"); - // console.log("doc: ", doc); - // } + let prefix = oldName.slice(0, firstUnderscoreIndex); + + if (!prefix || prefix === "undefined" || prefix === "") { + prefix = existingCollection?.collectionName as string; + // console.log(`⚠️ Prefix invalid, fallback used: ${prefix}`); + } + return `${prefix}_${newBaseName}`; + } + + referencedEdges = await collectionsModel(organization) + .find({ + isArchive: false, + projectId, + attributes: { + $elemMatch: { + "refKey.fieldId": new mongoose.Types.ObjectId(fieldId), + }, + }, + }) + .select("attributes projectId collectionName type"); + + if (referencedEdges.length) { + for (const doc of referencedEdges) { + let updated = false; - try { for (const oldattr of doc.attributes) { - console.log("oldattr: ", oldattr); const refKey = oldattr.refKey as any; - if ( - refKey?.collection_id?.toString() === - collectionNodeId.toString() && - refKey?.fieldId?.toString() === fieldId.toString() - ) { - const oldType = oldattr.type; - const newType = attr.type; - oldattr.type = newType as any; + if (refKey?.fieldId?.toString() === fieldId.toString()) { + const oldName = oldattr.key; + const updateName = attr.key; + const newKey = updateModelName(oldName, updateName); + oldattr.key = newKey; updated = true; } } if (updated) { - doc.markModified("attributes"); await doc.save(); } } catch (err: any) { @@ -839,9 +951,9 @@ export const UpdateAttributes = async ( { new: true } ); - console.log("editCollection", editCollection); + // console.log("editCollection", editCollection); } - return { status: "Success" }; + return { status: "Success", data: referencedEdges }; } } catch (error: unknown) { if (error instanceof Error) { @@ -1142,23 +1254,24 @@ export const DuplicateAttributes = async ( (attr: any) => attr.key.toLowerCase() === attrKey.toLowerCase() ); - console.log("existingAttr: ", existingAttr); if (existingAttr) { let counter = 1; let newKey = `${attrKey}(${counter})`; - console.log("attrKey: ", attrKey); - console.log("newKey: ", newKey); while ( existingCollection.attributes.some( (attr: any) => attr.key === newKey ) ) { - console.log("hi"); counter++; newKey = `${attrKey}(${counter})`; - console.log("newKey: ", newKey); } + + // existingCollection.attributes.push({ + // key: newKey, + // type: existingAttr.type, + // isArchive: false, + // }); } else { return { status: "Attribute doesnot match" }; } @@ -1230,7 +1343,6 @@ export const DelAttributes = async ( data: IAttributesDel ): Promise => { const { organization, userId, projectId, collectionNodeId, fieldId } = data; - console.log("data: ", data); try { const existingUser = await userModel(organization).findOne({ @@ -1251,7 +1363,6 @@ export const DelAttributes = async ( isArchive: false, "attributes._id": new mongoose.Types.ObjectId(fieldId), }); - console.log("existingCollection: ", existingCollection); if (!existingCollection) return { status: "Collection not found" }; const attribute = existingCollection.attributes.find( @@ -1315,7 +1426,6 @@ export const addAttributes = async ( ): Promise => { const { organization, projectId, userId, collectionNodeId, attributes } = data; - console.log("data: ", data); try { const existingUser = await userModel(organization).findOne({ @@ -1343,7 +1453,6 @@ export const addAttributes = async ( const duplicate = existingAttributes.find( (exAttr) => exAttr.key === attr.key && !exAttr.isArchive ); - console.log("duplicate: ", duplicate); if (duplicate) { return { status: `Attribute "${attr.key}" already exists` }; } diff --git a/src/shared/services/edgeService.ts b/src/shared/services/edgeService.ts index baa4e9b..035a2b6 100644 --- a/src/shared/services/edgeService.ts +++ b/src/shared/services/edgeService.ts @@ -26,9 +26,11 @@ interface IEdgeDelete { userId: string; edgeId: string; } -interface Attribute { +interface IAttribute { + _id?: string; key: string; type: string; + keyType?: string; isArchive: boolean; refKey?: { @@ -38,6 +40,7 @@ interface Attribute { } export const edgecreation = async (data: IEdge): Promise => { const { organization, projectId, from, to, cardinality, userId } = data; + console.log("data:edge ", data); try { const ExistingUser = await userModel(organization).findOne({ @@ -82,49 +85,84 @@ export const edgecreation = async (data: IEdge): Promise => { const collectionName = existingFromCollection.collectionName; const fieldName = from.field; - const newFieldKey = `${collectionName}_${capitalizeFirst(fieldName)}`; + // const newFieldKey = `${collectionName}_${capitalizeFirst(fieldName)}`; + // console.log("newFieldKey: ", newFieldKey); const fromField = existingFromCollection.attributes.find( (attr: any) => attr.key === from.field ); + if (!fromField) { + return { status: "From field not found" }; + } + const fromFieldId = [fromField].map((attr: any) => attr?._id)[0]; + console.log("fromFieldId: ", fromFieldId); + const resolvedKey = await resolveForeignKey( + organization, + existingFromCollection, + fromField as any + ); + console.log("resolvedKey: ", resolvedKey); - + const newFieldKey = `${resolvedKey.sourceCollectionName}_${capitalizeFirst( + resolvedKey.sourceFieldName + )}`; + + console.log('newFieldKey: ', newFieldKey); const fieldType = normalizeType(fromField?.type); + // console.log("fieldType: ", fieldType); const fieldExists = existingToCollection.attributes.some( (attr: any) => attr.key === newFieldKey ); - console.log('fieldExists: ', fieldExists); + // const keyType = + // fromField?.keyType === "primary" ? "foreign" : fromField?.keyType; + // console.log('keyType: ', keyType); if (!fieldExists) { const newAttribute: any = { key: newFieldKey, type: fieldType, + keyType: resolvedKey.keyType, + refKey: { - collection_id: existingFromCollection._id, - fieldId: fromFieldId, + // collection_id: existingFromCollection._id, + // fieldId: fromFieldId, + collection_id: resolvedKey.sourceCollectionId, + fieldId: resolvedKey.sourceFieldId, }, }; +const fieldExists = existingToCollection.attributes.some( + (attr: any) => attr.key === newFieldKey +); - existingToCollection.attributes.push(newAttribute); - console.log('newAttribute: ', newAttribute); +if (!fieldExists) { + existingToCollection.attributes.push(newAttribute); + await existingToCollection.save(); + console.log( + `✅ Added new foreign key '${newFieldKey}' in '${existingToCollection.collectionName}' referencing '${resolvedKey.sourceCollectionName}.${resolvedKey.sourceFieldName}'` + ); +} else { + console.log(`⚠️ Field '${newFieldKey}' already exists in '${existingToCollection.collectionName}'`); +} + // existingToCollection.attributes.push(newAttribute); + // existingToCollection.attributes = existingToCollection.attributes.map( + // (attr: any) => ({ + // ...attr, + // type: normalizeType(attr.type), + // }) + // ); - existingToCollection.attributes = existingToCollection.attributes.map( - (attr: any) => ({ - ...attr, - type: normalizeType(attr.type), - }) + // await existingToCollection.save(); + console.log("existingToCollection: ", existingToCollection); + const savedToCollection = await collectionsModel(organization).findById( + existingToCollection._id ); + const lastAttr = savedToCollection?.attributes?.at(-1); - await existingToCollection.save(); -const savedToCollection = await collectionsModel(organization).findById(existingToCollection._id); -const lastAttr = savedToCollection?.attributes?.at(-1); - -const fieldIdTo = (lastAttr as any)?._id; -console.log('fieldIdTo: ', fieldIdTo); + const fieldIdTo = (lastAttr as any)?._id; // console.log( // `Field ${newFieldKey} (type: ${fieldType}) added to TO collection with reference to ${existingFromCollection._id}` @@ -132,8 +170,12 @@ console.log('fieldIdTo: ', fieldIdTo); const newEdge = { projectId, - from: { collection_id: existingFromCollection._id, field: from.field,fieldId:fromFieldId }, - to: { collection_id: existingToCollection._id ,fieldId:fieldIdTo}, + from: { + collection_id: existingFromCollection._id, + field: from.field, + fieldId: fromFieldId, + }, + to: { collection_id: existingToCollection._id, fieldId: fieldIdTo }, cardinality, createdAt: new Date(), }; @@ -208,7 +250,6 @@ export const Alledges = async (data: IAllEdge): Promise => { }; export const deleteEdge = async (data: IEdgeDelete): Promise => { const { organization, projectId, edgeId, userId } = data; - console.log('data:edge ', data); try { const ExistingUser = await userModel(organization).findOne({ @@ -264,7 +305,6 @@ export const deleteEdge = async (data: IEdgeDelete): Promise => { console.log("Matched attribute not found in array"); } } else { - // console.log('No attribute matches the given collection_id'); return { status: "No deleted in matched Document" }; } } @@ -312,3 +352,133 @@ const normalizeType = (type: any): string => { return "string"; // fallback }; +interface IResolvedKey { + keyType: string; + sourceCollectionName: string; + sourceCollectionId: string; + sourceFieldId: string; + sourceFieldName: string; +} + +/** + * Resolves the true origin of a foreign key. + * If "fromField" is a foreign key, it recursively traces back + * to its original primary field and returns its reference info. + */ +// export const resolveForeignKey = async ( +// organization: string, +// fromCollection: any, +// fromField: IAttribute +// ): Promise => { +// if (!fromField) { +// return { +// keyType: "normal", +// sourceCollectionName: fromCollection.collectionName, +// sourceCollectionId: fromCollection._id?.toString?.() || "", +// sourceFieldId: "", +// sourceFieldName: "", +// }; +// } +// let keyType = fromField?.keyType || "normal"; +// let sourceCollectionName = fromCollection.collectionName; +// let sourceCollectionId = fromCollection._id?.toString?.() || ""; +// let sourceFieldId = fromField?._id?.toString?.() || ""; +// let sourceFieldName = fromField?.key || ""; + +// // 🔁 If the field is foreign → trace back to original primary +// if (fromField?.keyType === "foreign" && fromField?.refKey) { +// const refCol = await collectionsModel(organization).findById( +// fromField.refKey.collection_id +// ); + +// if (refCol) { +// const primaryAttr = (refCol.attributes as IAttribute[]).find( +// (attr) => attr?._id?.toString?.() === fromField.refKey?.fieldId +// ); + +// if (primaryAttr) { +// console.log("🔁 Tracing back to original primary field..."); + +// // Recursive call to ensure multi-level tracing +// return await resolveForeignKey(organization, refCol, primaryAttr); +// } +// } +// } + +// // ✅ If the field is primary, mark the propagated key as foreign +// if (fromField?.keyType === "primary") { +// keyType = "foreign"; +// } + +// return { +// keyType, +// sourceCollectionName, +// sourceCollectionId, +// sourceFieldId, +// sourceFieldName, +// }; +// }; +export const resolveForeignKey = async ( + organization: string, + fromCollection: any, + fromField?: IAttribute +): Promise => { + if (!fromField || !fromCollection) { + return { + keyType: "normal", + sourceCollectionName: fromCollection?.collectionName || "", + sourceCollectionId: fromCollection?._id?.toString?.() || "", + sourceFieldId: "", + sourceFieldName: "", + }; + } + + let keyType = fromField.keyType || "normal"; + let sourceCollectionName = fromCollection.collectionName; + let sourceCollectionId = fromCollection._id?.toString?.() || ""; + let sourceFieldId = fromField._id?.toString?.() || ""; + let sourceFieldName = fromField.key || ""; + + // 🔁 Recursive tracing for both FOREIGN and SECONDARY with refKey + if ( + (fromField.keyType === "foreign" || fromField.keyType === "secondary") && + fromField.refKey + ) { + const refCol = await collectionsModel(organization).findById( + fromField.refKey.collection_id + ); + + if (refCol) { + const refField = (refCol.attributes as IAttribute[]).find( + (attr) => attr?._id?.toString?.() === fromField.refKey?.fieldId + ); + + if (refField) { + console.log( + `🔁 Tracing ${fromField.keyType} key ${fromField.key} → ${refCol.collectionName}.${refField.key}` + ); + // Recursive call — trace until primary + return await resolveForeignKey(organization, refCol, refField); + } else { + console.warn( + `⚠️ Field not found for refKey.fieldId: ${fromField.refKey?.fieldId}` + ); + } + } else { + console.warn( + `⚠️ Collection not found for refKey.collection_id: ${fromField.refKey?.collection_id}` + ); + } + } + + // ✅ Convert primary to foreign for propagation + if (fromField.keyType === "primary") keyType = "foreign"; + + return { + keyType, + sourceCollectionName, + sourceCollectionId, + sourceFieldId, + sourceFieldName, + }; +}; diff --git a/src/shared/services/fileFunctionalityService.ts b/src/shared/services/fileFunctionalityService.ts index 24eaab2..efb993c 100644 --- a/src/shared/services/fileFunctionalityService.ts +++ b/src/shared/services/fileFunctionalityService.ts @@ -32,9 +32,7 @@ export const folderProject = async ( return result; }; const FILE_BACKEND_URL = process.env.FILE_BACKEND_URL; - console.log('FILE_BACKEND_URL: ', FILE_BACKEND_URL); export const modelNodeFile = async (data: { projectName: string; models1: any[] }) => { - console.log('data:test ', data); const FILE_BACKEND_URL = process.env.FILE_BACKEND_URL; if (!FILE_BACKEND_URL) throw new Error("❌ FILE_BACKEND_URL not set"); diff --git a/src/shared/services/projectService.ts b/src/shared/services/projectService.ts index 31fb730..d99f459 100644 --- a/src/shared/services/projectService.ts +++ b/src/shared/services/projectService.ts @@ -229,7 +229,6 @@ export const projectDatas = async (data: IgetProject): Promise => { }) .skip(skipdata) .limit(limitdata); - console.log('projectDatas: ', projectDatas); if (!projectDatas) return { status: "No project found" }; // const formattedProjects = projectDatas.map((proj: any) => { diff --git a/src/socket-server/controllers/collectionNodeController.ts b/src/socket-server/controllers/collectionNodeController.ts index 6fc31fc..6076df6 100644 --- a/src/socket-server/controllers/collectionNodeController.ts +++ b/src/socket-server/controllers/collectionNodeController.ts @@ -5,7 +5,7 @@ import { FinalResponse, validateFields, } from "../utils/socketfunctionHelpers"; -import { emitToSenderAndAdmins } from "../utils/emitEventResponse"; +import { emitToRoom } from "../utils/emitEventResponse"; import { deleteEdge, edgecreation } from "../../shared/services/edgeService"; import { addAttributes, @@ -31,14 +31,20 @@ export const CollectionHandleEvent = async ( const missingFields = validateFields(data, requiredFields); if (missingFields.length > 0) { - emitToSenderAndAdmins( - io, - socket, - data.organization, - EVENTS.collectionCreateResponse, - ErrorResponse(missingFields, socket, data.organization), - connectedUsersByOrg - ); + let room: string | undefined; + + if (data?.projectId) { + room = data.projectId.toString(); + } + if (room) { + emitToRoom( + io, + socket, + room, + EVENTS.collectionCreateResponse, + ErrorResponse(missingFields, socket, room) + ); + } return; } const result = await Nodecreation(data); @@ -63,15 +69,15 @@ export const CollectionHandleEvent = async ( messages, result_Datas ); + let room: string | undefined; + if (data?.projectId) { + room = data.projectId.toString(); + } - emitToSenderAndAdmins( - io, - socket, - data.organization, - EVENTS.collectionCreateResponse, - response, - connectedUsersByOrg - ); + if (room) { + console.log('room: ', room); + emitToRoom(io, socket, room, EVENTS.collectionCreateResponse, response); + } }; export const CollectionUpdateHandleEvent = async ( event: string, @@ -87,18 +93,24 @@ export const CollectionUpdateHandleEvent = async ( const missingFields = validateFields(data, requiredFields); if (missingFields.length > 0) { - emitToSenderAndAdmins( - io, - socket, - data.organization, - EVENTS.collectionUpdateResponse, - ErrorResponse(missingFields, socket, data.organization), - connectedUsersByOrg - ); + let room: string | undefined; + + if (data?.projectId) { + room = data.projectId.toString(); + } + if (room) { + emitToRoom( + io, + socket, + room, + EVENTS.collectionUpdateResponse, + ErrorResponse(missingFields, socket, room) + ); + } return; } const result = await updatecollection(data); - console.log('result: ', result); + const status = typeof result?.status === "string" ? result.status : "unknown"; const messages: Record = { @@ -107,10 +119,9 @@ export const CollectionUpdateHandleEvent = async ( "User not found": { message: "User not found" }, "Collection not found": { message: "Collection not found" }, "Invalid ID": { message: "nvalid ID provided" }, - }; const result_Datas = - status === "Success" && result?.data ? result.data : undefined; + status === "Success" && result?.data ? result.data : undefined; const response = FinalResponse( status, @@ -119,15 +130,14 @@ export const CollectionUpdateHandleEvent = async ( messages, result_Datas ); + let room: string | undefined; + if (data?.projectId) { + room = data?.projectId; + } - emitToSenderAndAdmins( - io, - socket, - data.organization, - EVENTS.collectionUpdateResponse, - response, - connectedUsersByOrg - ); + if (room) { + emitToRoom(io, socket, room, EVENTS.collectionUpdateResponse, response); + } }; export const CollectioDeleteHandleEvent = async ( @@ -144,14 +154,20 @@ export const CollectioDeleteHandleEvent = async ( const missingFields = validateFields(data, requiredFields); if (missingFields.length > 0) { - emitToSenderAndAdmins( - io, - socket, - data.organization, - EVENTS.collectionDeleteResponse, - ErrorResponse(missingFields, socket, data.organization), - connectedUsersByOrg - ); + let room: string | undefined; + + if (data?.projectId) { + room = data.projectId.toString(); + } + if (room) { + emitToRoom( + io, + socket, + room, + EVENTS.collectionDeleteResponse, + ErrorResponse(missingFields, socket, room) + ); + } return; } const result = await delCollection(data); @@ -174,15 +190,14 @@ export const CollectioDeleteHandleEvent = async ( messages, result_Datas ); + let room: string | undefined; + if (data?.projectId) { + room = data?.projectId; + } - emitToSenderAndAdmins( - io, - socket, - data.organization, - EVENTS.collectionDeleteResponse, - response, - connectedUsersByOrg - ); + if (room) { + emitToRoom(io, socket, room, EVENTS.collectionDeleteResponse, response); + } }; export const CollectioDuplicateHandleEvent = async ( event: string, @@ -198,14 +213,20 @@ export const CollectioDuplicateHandleEvent = async ( const missingFields = validateFields(data, requiredFields); if (missingFields.length > 0) { - emitToSenderAndAdmins( - io, - socket, - data.organization, - EVENTS.collectionDeleteResponse, - ErrorResponse(missingFields, socket, data.organization), - connectedUsersByOrg - ); + let room: string | undefined; + + if (data?.projectId) { + room = data.projectId.toString(); + } + if (room) { + emitToRoom( + io, + socket, + room, + EVENTS.collectionDeleteResponse, + ErrorResponse(missingFields, socket, room) + ); + } return; } const result = await DuplicateCollection(data); @@ -229,15 +250,14 @@ export const CollectioDuplicateHandleEvent = async ( messages, result_Datas ); + let room: string | undefined; + if (data?.projectId) { + room = data?.projectId; + } - emitToSenderAndAdmins( - io, - socket, - data.organization, - EVENTS.collectionDuplicateResponse, - response, - connectedUsersByOrg - ); + if (room) { + emitToRoom(io, socket, room, EVENTS.collectionDuplicateResponse, response); + } }; export const setAttributesHandleEvent = async ( event: string, @@ -258,14 +278,20 @@ export const setAttributesHandleEvent = async ( const missingFields = validateFields(data, requiredFields); if (missingFields.length > 0) { - emitToSenderAndAdmins( - io, - socket, - data.organization, - EVENTS.collectionAttributeSetResponse, - ErrorResponse(missingFields, socket, data.organization), - connectedUsersByOrg - ); + let room: string | undefined; + + if (data?.projectId) { + room = data.projectId.toString(); + } + if (room) { + emitToRoom( + io, + socket, + room, + EVENTS.collectionAttributeSetResponse, + ErrorResponse(missingFields, socket, room) + ); + } return; } const result = await addAttributes(data); @@ -288,15 +314,20 @@ export const setAttributesHandleEvent = async ( messages, result_Datas ); + let room: string | undefined; + if (data?.projectId) { + room = data?.projectId; + } - emitToSenderAndAdmins( - io, - socket, - data.organization, - EVENTS.collectionAttributeSetResponse, - response, - connectedUsersByOrg - ); + if (room) { + emitToRoom( + io, + socket, + room, + EVENTS.collectionAttributeSetResponse, + response + ); + } }; export const AttributesDeleteHandleEvent = async ( event: string, @@ -317,14 +348,20 @@ export const AttributesDeleteHandleEvent = async ( const missingFields = validateFields(data, requiredFields); if (missingFields.length > 0) { - emitToSenderAndAdmins( - io, - socket, - data.organization, - EVENTS.collectionAttributeDeleteResponse, - ErrorResponse(missingFields, socket, data.organization), - connectedUsersByOrg - ); + let room: string | undefined; + + if (data?.projectId) { + room = data.projectId.toString(); + } + if (room) { + emitToRoom( + io, + socket, + room, + EVENTS.collectionAttributeDeleteResponse, + ErrorResponse(missingFields, socket, room) + ); + } return; } const result = await DelAttributes(data); @@ -342,9 +379,7 @@ export const AttributesDeleteHandleEvent = async ( const result_Datas = status === "Success" && result?.data ? result.data : undefined; const result1_Datas = - (status === "Success") && result?.data - ? result.data - : undefined; + status === "Success" && result?.data ? result.data : undefined; const response = FinalResponse( status, socket, @@ -352,15 +387,20 @@ export const AttributesDeleteHandleEvent = async ( messages, result_Datas ); + let room: string | undefined; + if (data?.projectId) { + room = data?.projectId; + } - emitToSenderAndAdmins( - io, - socket, - data.organization, - EVENTS.collectionAttributeDeleteResponse, - response, - connectedUsersByOrg - ); + if (room) { + emitToRoom( + io, + socket, + room, + EVENTS.collectionAttributeDeleteResponse, + response + ); + } }; export const AttributesUpdateHandleEvent = async ( event: string, @@ -372,22 +412,24 @@ export const AttributesUpdateHandleEvent = async ( } ) => { if (event !== EVENTS.collectionAttributeUpdate || !data?.organization) return; - const requiredFields = [ - "collectionNodeId", - "projectId", - "organization", - ]; + const requiredFields = ["collectionNodeId", "projectId", "organization"]; const missingFields = validateFields(data, requiredFields); if (missingFields.length > 0) { - emitToSenderAndAdmins( - io, - socket, - data.organization, - EVENTS.collectionAttributeUpdateResponse, - ErrorResponse(missingFields, socket, data.organization), - connectedUsersByOrg - ); + let room: string | undefined; + + if (data?.projectId) { + room = data.projectId.toString(); + } + if (room) { + emitToRoom( + io, + socket, + room, + EVENTS.collectionAttributeUpdateResponse, + ErrorResponse(missingFields, socket, room) + ); + } return; } const result = await UpdateAttributes(data); @@ -402,9 +444,7 @@ export const AttributesUpdateHandleEvent = async ( const msg = messages[status] || { message: "Internal server error" }; const result_Datas = - (status === "Success") && result?.data - ? result.data - : undefined; + status === "Success" && result?.data ? result.data : undefined; const response = FinalResponse( status, socket, @@ -412,13 +452,18 @@ export const AttributesUpdateHandleEvent = async ( messages, result_Datas ); + let room: string | undefined; + if (data?.projectId) { + room = data?.projectId; + } - emitToSenderAndAdmins( - io, - socket, - data.organization, - EVENTS.collectionAttributeUpdateResponse, - response, - connectedUsersByOrg - ); + if (room) { + emitToRoom( + io, + socket, + room, + EVENTS.collectionAttributeUpdateResponse, + response + ); + } }; diff --git a/src/socket-server/controllers/edgeController.ts b/src/socket-server/controllers/edgeController.ts index 3413694..9571ab9 100644 --- a/src/socket-server/controllers/edgeController.ts +++ b/src/socket-server/controllers/edgeController.ts @@ -37,7 +37,6 @@ export const edgeHandleEvent = async ( } const result = await edgecreation(data); - console.log('result: ', result); const status = typeof result?.status === "string" ? result.status : "unknown"; const messages: Record = { @@ -55,7 +54,6 @@ export const edgeHandleEvent = async ( const msg = messages[status] || { message: "Internal server error" }; const result_Datas = status === "Success" && result?.data ? result.data : undefined; - console.log('result_Datas: ', result_Datas); const response = FinalResponse( status, @@ -64,7 +62,6 @@ export const edgeHandleEvent = async ( messages, result_Datas ); - console.log('response: ', response); emitToSenderAndAdmins( io, @@ -104,7 +101,6 @@ export const deleteEdgeHandleEvent = async ( return; } const result = await deleteEdge(data); - console.log('result: ', result); const status = typeof result?.status === "string" ? result.status : "unknown"; const messages: Record = { @@ -122,7 +118,6 @@ export const deleteEdgeHandleEvent = async ( messages, result_Datas ); - console.log('response: ', response); emitToSenderAndAdmins( io, diff --git a/src/socket-server/controllers/projectController.ts b/src/socket-server/controllers/projectController.ts index b7d7f41..0b0641a 100644 --- a/src/socket-server/controllers/projectController.ts +++ b/src/socket-server/controllers/projectController.ts @@ -48,7 +48,6 @@ export const projectHandleEvent = async ( } const subBkd = process.env.FILE_BACKEND_URL; if (!subBkd) { - console.log('!subBkd: ', subBkd); // res.status(500).json({ // message: "mainBkdUrl not configured", // }); @@ -186,7 +185,6 @@ export const projectDeleteHandleEvent = async ( return; } const result = await DeleteProject(data); - console.log('result: ', result); const status = typeof result?.status === "string" ? result.status : "unknown"; const messages: Record = { diff --git a/src/socket-server/manager/manager.ts b/src/socket-server/manager/manager.ts index 259294f..a63296d 100644 --- a/src/socket-server/manager/manager.ts +++ b/src/socket-server/manager/manager.ts @@ -2,6 +2,7 @@ import { Server, Socket } from "socket.io"; import jwt from "jsonwebtoken"; import { eventHandlerMap } from "../events/eventHandaler"; import { existingUserData } from "../../shared/services/AuthService"; +import { joinRoom, leaveRoom } from "../utils/room"; interface UserPayload { userId: string; @@ -111,12 +112,30 @@ export const SocketServer = async (io: Server) => { organization, }); // console.log(`✅ Connected to namespace ${nspName}: ${socket.id}`); + socket.on("joinRoom", (data) => { + if (!data?.projectId) { + console.warn("❌ joinRoom missing projectId or versionId"); + return; + } + joinRoom(io, socket, { + projectId: data.projectId, - // Common event handler + userId, + organization, + }); + }); +// Group api socket event creation and some bug modification + // ✅ Optional leave room listener + socket.on("leaveRoom", (data) => { + if (!data?.projectId) return; + leaveRoom(io, socket, { + projectId: data.projectId, + organization, + userId, + }); + }); socket.onAny((event: string, data: any, callback: any) => { - // console.log("🔥 Incoming socket event:", event); const handler = eventHandlerMap[event]; -// console.log("handler:", handler ? "✅ found" : "❌ undefined"); if (handler) { handler(event, socket, io, data, connectedUsersByOrg, callback); } else { diff --git a/src/socket-server/utils/emitEventResponse.ts b/src/socket-server/utils/emitEventResponse.ts index a940255..97668fb 100644 --- a/src/socket-server/utils/emitEventResponse.ts +++ b/src/socket-server/utils/emitEventResponse.ts @@ -91,11 +91,11 @@ export const emitToRoom = ( organization: result.organization, }; - console.log("🔥 EMIT TO ROOM:"); - console.log("➡️ Event: ", event); + // console.log("🔥 EMIT TO ROOM:"); + // console.log("➡️ Event: ", event); console.log("📦 Full result input: ", result); // 👉 what you passed from observer - console.log("📤 Final payload being emitted: ", payload); // 👉 actual emitted payload - console.log("🏠 Room: ", room); + // console.log("📤 Final payload being emitted: ", payload); // 👉 actual emitted payload + // console.log("🏠 Room: ", room); socket.to(room).emit(event, payload); diff --git a/src/socket-server/utils/room.ts b/src/socket-server/utils/room.ts new file mode 100644 index 0000000..8589540 --- /dev/null +++ b/src/socket-server/utils/room.ts @@ -0,0 +1,125 @@ +import { Server, Socket } from "socket.io"; +import { emitToRoom } from "./emitEventResponse"; +import userModel from "../../shared/model/userModel"; +import { EVENTS } from "../events/events"; + +const userFollowMap = new Map(); // socketId -> followingUser + +interface JoinRoomInput { projectId: string; userId: string; organization: string; } +interface LeaveRoomInput { projectId: string; userId: string; organization: string; } + +// Build room data including followingUser +// export async function buildRoomDatas(socket: Socket, projectId: string, organization: string) { +// let roomDatas: { roomName: string; Users: string[]; UsersDetails: any[] } | null = null; +// const rooms = socket.nsp.adapter.rooms; + +// for (const [roomName, socketsSet] of rooms) { +// if (socket.nsp.sockets.get(roomName)) continue; // skip personal room + +// // Collect all users in the room +// const roomUsers = [...socketsSet].map(id => { +// const s = socket.nsp.sockets.get(id); +// return { +// userId: s?.data.userId || id, +// socketId: id // store socketId for followUser map lookup +// }; +// }); + +// const userIds = roomUsers.map(u => u.userId); + +// const usersFromDB = await userModel(organization).find({ _id: { $in: userIds }, isArchive: false }); + +// const userdatas = usersFromDB.flatMap(user => { +// const userCams = cameraFromDB.filter(cam => cam.userId.toString() === user._id.toString()); + +// // find the socketId of this user in the room +// const userSocketId = roomUsers.find(u => u.userId === user._id.toString())?.socketId; +// const followingUser = userSocketId ? userFollowMap.get(userSocketId) || null : null; + +// if (userCams.length === 0) { +// return [{ +// userId: user._id.toString(), +// userName: user.userName, +// projectId, +// versionId: "", +// camData: { position: { x:0, y:40, z:30 }, rotation: { x:0, y:0, z:0 }, target: { x:0, y:0, z:0 } }, +// followingUser +// }]; +// } + +// return userCams.map(cam => ({ +// userId: user._id.toString(), +// userName: user.userName, +// projectId: cam.projectId.toString(), +// versionId: cam.versionId?.toString() || "", +// camData: { position: cam.position, rotation: cam.rotation, target: cam.target }, +// followingUser +// })); +// }); + +// roomDatas = { roomName, Users: roomUsers.map(u => u.userId), UsersDetails: userdatas }; +// break; // only process the first room +// } + +// return roomDatas || { roomName: projectId, Users: [], UsersDetails: [] }; +// } + + +// Join Room +export const joinRoom = async (io: Server, socket: Socket, data: JoinRoomInput) => { + const { projectId, organization, userId } = data; + socket.data.userId = userId; + const room = `${projectId}`; + await socket.join(room); + + // const roomDatas = await buildRoomDatas(socket, projectId, organization); + + // emitToRoom(io, socket, room, EVENTS.roomUserData_v1, { + // // data: roomDatas, + // socketId: socket.id + // }); + + return room; +}; + +// Leave Room +export const leaveRoom = async (io: Server, socket: Socket, data: LeaveRoomInput) => { + const { projectId, userId, organization } = data; + const room = `${projectId}`; + socket.leave(room); + + // const roomDatas = await buildRoomDatas(socket, projectId, organization); + + // emitToRoom(io, socket, room, EVENTS.roomUserData_v1, { + // data: roomDatas || { roomName: projectId, Users: [], UsersDetails: [] }, + // message: `${userId} left the room`, + // socketId: socket.id + // }); + + userFollowMap.delete(socket.id); + + return room; +}; + +// Follow User +// export const setupFollowUser = (io: Server, socket: Socket) => { +// socket.on(EVENTS.roomUserData_setV1, async (data: { projectId: string; followingUser: string; organization: string }) => { +// const { projectId, followingUser, organization } = data; + +// userFollowMap.set(socket.id, followingUser); + +// const roomDatas = await buildRoomDatas(socket, projectId, organization); +// const room = `${projectId}`; + +// // emitToRoom(io, socket, room, EVENTS.roomUserData_v1, { +// // data: roomDatas, +// // socketId: socket.id +// // }); + +// // console.log(`User ${socket.data.userId} followed: ${followingUser}`); +// }); + +// socket.on("disconnect", () => { +// userFollowMap.delete(socket.id); +// }); +// };