From 46956974a18ad81ab52230a9fe4313e7de03d446 Mon Sep 17 00:00:00 2001 From: sabarinathan Date: Wed, 27 Aug 2025 15:30:25 +0530 Subject: [PATCH] Add edge model, service, and routes; integrate edge creation and deletion APIs --- .env | 2 +- src/api-server/app.ts | 2 + src/api-server/controller/edgeController.ts | 158 ++++++++++ src/api-server/routes/edgeRoutes.ts | 8 + src/shared/model/collectionModel.ts | 62 +++- src/shared/model/edgeModel.ts | 29 ++ src/shared/services/edgeService.ts | 307 ++++++++++++++++++++ 7 files changed, 554 insertions(+), 14 deletions(-) create mode 100644 src/api-server/controller/edgeController.ts create mode 100644 src/api-server/routes/edgeRoutes.ts create mode 100644 src/shared/model/edgeModel.ts create mode 100644 src/shared/services/edgeService.ts diff --git a/.env b/.env index ab4fe19..42a068d 100644 --- a/.env +++ b/.env @@ -1,4 +1,4 @@ -MONGO_URI=mongodb://192.168.0.110/ +MONGO_URI=mongodb://192.168.0.111/ MONGO_USER=mydata MONGO_PASSWORD=mongodb@hexr2002 MONGO_AUTH_DB=admin diff --git a/src/api-server/app.ts b/src/api-server/app.ts index 758a034..d75d459 100644 --- a/src/api-server/app.ts +++ b/src/api-server/app.ts @@ -3,6 +3,7 @@ import cors from "cors"; import dotenv from "dotenv"; import projectRoutes from "./routes/projectRoutes"; import collectionNodeRoutes from "./routes/collectionRoutes"; +import edgeRoutes from "./routes/edgeRoutes"; dotenv.config({ quiet: true }); const app = express(); @@ -14,5 +15,6 @@ app.use( app.use("/api/v1", projectRoutes); app.use("/api/v1", collectionNodeRoutes); +app.use("/api/v1", edgeRoutes); export default app; diff --git a/src/api-server/controller/edgeController.ts b/src/api-server/controller/edgeController.ts new file mode 100644 index 0000000..0b2eac2 --- /dev/null +++ b/src/api-server/controller/edgeController.ts @@ -0,0 +1,158 @@ +import { Request, Response } from "express"; +import { Alledges, deleteEdge, edgecreation } from "../../shared/services/edgeService"; +export const edgeCreationController = async ( + req: Request, + res: Response +): Promise => { + try { + const { organization, projectId, from,to,cardinality } = req.body; + if (!organization || !projectId || !from || !to) { + res.status(400).json({ + message: "All fields are required", + }); + return; + } + const data = { + organization, + projectId, +from, +to, +cardinality + }; + const result = await edgecreation(data); + console.log('result: ', result); + + switch (result.status) { + case "project not found": + res.status(200).json({ + message: "project not found", + }); + break; + case "From collection not found": + res.status(200).json({ + message: "From collection not found", + }); + break; + case "To collection not found": + res.status(200).json({ + message: "To collection not found", + }); + break; + case "Field already exists": + res.status(200).json({ + message: "Field already exists", + }); + break; + case "Success": + res.status(200).json({ + message:"Edge created successfully", + collectionNodeId: result.data, + }); + break; + default: + res.status(500).json({ + message: "Internal server error", + }); + break; + } + } catch (error) { + res.status(500).json({ + message: "Unknown error", + }); + } +}; +export const allEdgesController = async ( + req: Request, + res: Response +): Promise => { + try { + const { organization, projectId, } = req.body; + if (!organization || !projectId ) { + res.status(400).json({ + message: "All fields are required", + }); + return; + } + const data = { + organization, + projectId, + }; + const result = await Alledges(data); + + switch (result.status) { + case "project not found": + res.status(200).json({ + message: "project not found", + }); + break; + case "edge not found": + res.status(200).json({ + message: "edge not found", + }); + break; + case "Success": + res.status(200).json({ + message:"fetch all Edge datas successfully", + collectionNodeId: result.data, + }); + break; + default: + res.status(500).json({ + message: "Internal server error", + }); + break; + } + } catch (error) { + res.status(500).json({ + message: "Unknown error", + }); + } +}; +export const deleteEdgesController = async ( + req: Request, + res: Response +): Promise => { + try { + const { organization, projectId, edgeId} = req.body; + if (!organization || !projectId ||!edgeId) { + res.status(400).json({ + message: "All fields are required", + }); + return; + } + const data = { + organization, + projectId, + edgeId + }; + const result = await deleteEdge(data); + + switch (result.status) { + case "project not found": + res.status(200).json({ + message: "project not found", + }); + break; + case "edge not found": + res.status(200).json({ + message: "edge not found", + }); + break; + case "Success": + res.status(200).json({ + message:"Edge deleted successfully", + collectionNodeId: result.data, + }); + break; + default: + res.status(500).json({ + message: "Internal server error", + }); + break; + } + } catch (error) { + res.status(500).json({ + message: "Unknown error", + }); + } +}; \ No newline at end of file diff --git a/src/api-server/routes/edgeRoutes.ts b/src/api-server/routes/edgeRoutes.ts new file mode 100644 index 0000000..b9cd86a --- /dev/null +++ b/src/api-server/routes/edgeRoutes.ts @@ -0,0 +1,8 @@ +import express from "express"; +import { allEdgesController, deleteEdgesController, edgeCreationController } from "../controller/edgeController"; +const edgeRoutes = express.Router(); + +edgeRoutes.post("/edgeCreate", edgeCreationController); +edgeRoutes.patch("/edgeDelete", deleteEdgesController); +edgeRoutes.get("/allEdges", allEdgesController); +export default edgeRoutes; diff --git a/src/shared/model/collectionModel.ts b/src/shared/model/collectionModel.ts index b140891..becae61 100644 --- a/src/shared/model/collectionModel.ts +++ b/src/shared/model/collectionModel.ts @@ -1,30 +1,66 @@ import { Schema, Document } from "mongoose"; import MainModel from "../connection/connection"; import { IProject } from "./projectmodel"; +type IattributeTypes = + | "string" + | "any" + | "Array" + | "Date" + | "Enum" + | "undefined" + | "object" + | "ObjectId" + | "number" + | "boolean"; + +interface IAttributes { + isArchive: boolean; + key: string; + type: IattributeTypes; + refKey?: object; + required?: boolean; + default?: any; + unique?: boolean; + index?: boolean; +} interface ICollectionNode extends Document { projectId: IProject["_id"]; collectionNodeName: string; - attributes: [ - { - key: string; - type: any; - } - ]; + attributes: IAttributes[]; isArchive: boolean; position: [number, number, number]; } +const attributeSchema = new Schema({ + key: { type: String, required: true }, + type: { + type: String, + required: true, + enum: [ + "string", + "number", + "boolean", + "Date", + "ObjectId", + "Array", + "Enum", + "object", + "any", + ], + }, + required: { type: Boolean }, + refKey: { type: Object }, + default: { type: Schema.Types.Mixed }, + unique: { type: Boolean }, + index: { type: Boolean }, + isArchive: { type: Boolean, default: false }, +}); const collectionSchema: Schema = new Schema( { projectId: { type: Schema.Types.ObjectId, ref: "Project" }, collectionNodeName: { type: String }, position: { type: [Number], required: true }, isArchive: { type: Boolean, default: false }, - attributes: [ - { - key: { type: String }, - type: { type: Schema.Types.Mixed }, - }, - ], + attributes: [attributeSchema], }, { timestamps: true, @@ -34,4 +70,4 @@ const collectionSchema: Schema = new Schema( const collectionsModel = (db: any) => { return MainModel(db, "collectionNode", collectionSchema, "collectionNode"); }; -export default collectionsModel; +export default collectionsModel; \ No newline at end of file diff --git a/src/shared/model/edgeModel.ts b/src/shared/model/edgeModel.ts new file mode 100644 index 0000000..4e7ba01 --- /dev/null +++ b/src/shared/model/edgeModel.ts @@ -0,0 +1,29 @@ +import { Schema, Document } from "mongoose"; +import MainModel from "../connection/connection"; +import { IProject } from "./projectmodel"; +interface IEdgeModel extends Document { + projectId: IProject["_id"]; + from: { collection_id: string; field: string }; + to: { collection_id: string }; + cardinality: "one-to-one" | "one-to-many"|"many-to-many" + isArchive: boolean; + createdAt: number; + +} +const EdgeSchema = new Schema({ + projectId: { type: Schema.Types.ObjectId, ref: "Project", required: true }, + from: { + collection_id: { type: String, required: true }, + field: { type: String, required: true }, + }, + to: { + collection_id: { type: String, required: true }, + }, + cardinality: { type: String, enum: ["one-to-one", "one-to-many", "many-to-many"], required: true }, + isArchive: { type: Boolean, default: false }, + createdAt: { type: Number, default: Date.now }, +}); +const edgeModel = (db: any) => { + return MainModel(db, "edge", EdgeSchema, "edge"); +}; +export default edgeModel; diff --git a/src/shared/services/edgeService.ts b/src/shared/services/edgeService.ts new file mode 100644 index 0000000..1b50f14 --- /dev/null +++ b/src/shared/services/edgeService.ts @@ -0,0 +1,307 @@ +import ProjectType from "../../shared/model/projectmodel"; +import collectionsModel from "../model/collectionModel"; +import edgeModel from "../model/edgeModel"; +interface Iresponse { + status: string; + data?: any; +} +interface IEdge { + organization: string; + projectId: string; + from: { collection_id: string, field: string }; + to: { collection_id: string }; + cardinality: string +} +interface IAllEdge { + organization: string; + projectId: string; +} +interface IEdgeDelete { + organization: string; + projectId: string; + edgeId: string; +} +interface Attribute { + key: string; + type: string; + isArchive: boolean; + + refKey?: { + collection_id: any; + fieldId: any; + }; + +} +export const edgecreation = async ( + data: IEdge +): Promise => { + const { organization, projectId, from, to, cardinality } = data; + console.log('data: ', data); + try { + const existingProject = await ProjectType(organization).findOne({ + _id: projectId, + // createdBy: userName, + isArchive: false, + }); + + if (!existingProject) { + return { status: "project not found" }; + } + + const existingFromCollection = await collectionsModel(organization).findOne({ + _id: from.collection_id + }) + + if (!existingFromCollection) { + return { status: "From collection not found" }; + } + const existingToCollection = await collectionsModel(organization).findOne({ + _id: to.collection_id + }) + if (!existingToCollection) { + return { status: "To collection not found" }; + } + + + // ✅ Generate new field name for TO collection + const capitalizeFirst = (str: string) => { + if (!str) return str; + return str.charAt(0).toUpperCase() + str.slice(1); + }; + + // Example variables (replace with your actual data) + const collectionName = existingFromCollection.collectionNodeName; + const fieldName = from.field; + + // ✅ Generate new field key + const newFieldKey = `${collectionName}${capitalizeFirst(fieldName)}`; + console.log('newFieldKey: ', newFieldKey); + // Find matching field in FROM collection + const fromField = existingFromCollection.attributes.find( + (attr: any) => attr.key === from.field + ); + const fromFieldId = [fromField].map((attr: any) => attr?._id)[0]; + + + + const fieldType = normalizeType(fromField?.type); + + // ✅ Check if field already exists in TO collection + // Check if already exists in TO collection + const fieldExists = existingToCollection.attributes.some( + (attr: any) => attr.key === newFieldKey + ); + + if (!fieldExists) { + + // ✅ Add field if not exists + const newAttribute: any = { + key: newFieldKey, + type: fieldType, + refKey: { + collection_id: existingFromCollection._id, + fieldId: fromFieldId + } + }; + + // ✅ Push the object directly + console.log('newAttribute: ', newAttribute); + existingToCollection.attributes.push(newAttribute); + + existingToCollection.attributes = existingToCollection.attributes.map((attr: any) => ({ + ...attr, + type: normalizeType(attr.type), + })); + + await existingToCollection.save(); + + + console.log( + `Field ${newFieldKey} (type: ${fieldType}) added to TO collection with reference to ${existingFromCollection._id}` + ); + const newEdge = { + projectId, + from: { collection_id: existingFromCollection._id, field: from.field }, + to: { collection_id: existingToCollection._id }, + cardinality, + createdAt: new Date(), + }; + + // Here you should save into EdgeModel (need Edge Schema) + // Example: + const savedEdge = await edgeModel(organization).create(newEdge); + + // + return { + status: "Success", + data: savedEdge, // replace with savedEdge after model integration + }; + } + else { + console.log("Field already exists 🚫"); + return { + status: "Field already exists" + } + + + + + } + } + catch (error: unknown) { + console.log('error: ', error); + if (error instanceof Error) { + return { + status: error.message, + }; + } else { + return { + status: "An unexpected error occurred", + }; + } + } +}; + +export const Alledges = async ( + data: IAllEdge +): Promise => { + const { organization, projectId, } = data; + console.log('data: ', data); + try { + const existingProject = await ProjectType(organization).findOne({ + _id: projectId, + // createdBy: userName, + isArchive: false, + }); + + if (!existingProject) { + return { status: "project not found" }; + } + const edgeDatas = await edgeModel(organization).find() + console.log('edgeDatas: ', edgeDatas); + + + if (!edgeDatas || edgeDatas.length === 0) { + return { status: "edge not found" }; + } + + return { + status: "Success", + data: edgeDatas, // replace with savedEdge after model integration + }; + + + + } catch (error: unknown) { + console.log('error: ', error); + if (error instanceof Error) { + return { + status: error.message, + }; + } else { + return { + status: "An unexpected error occurred", + }; + } + } +}; +export const deleteEdge = async ( + data: IEdgeDelete +): Promise => { + const { organization, projectId, edgeId } = data; + console.log('data: ', data); + try { + const existingProject = await ProjectType(organization).findOne({ + _id: projectId, + // createdBy: userName, + isArchive: false, + }); + + if (!existingProject) { + return { status: "project not found" }; + } + + const deleteEdge = await edgeModel(organization).findOneAndUpdate + ({ _id: edgeId, isArchive: false }, { + isArchive: true + }, { new: true }) + if (!deleteEdge) return { status: "Edge not found" } + const collectionDoc = await collectionsModel(organization).findOne({ + _id: deleteEdge?.to?.collection_id, isArchive: false + }); + + if (!collectionDoc ) { + return { status: "collection not found" }; + } + else { + const attributes = collectionDoc.attributes || []; + const matchedAttr = attributes.find((attr, index) => { + const refKey: any = (attr as any).refKey; + if (refKey) { + return String(refKey.collection_id) === String(deleteEdge?.from?.collection_id); + } + return false; + }); + if (matchedAttr) { + console.log('Matched Attribute:', matchedAttr); + const index = attributes.indexOf(matchedAttr); + if (index > -1) { + attributes.splice(index, 1); + await collectionDoc.save(); + console.log(`Attribute at index ${index} deleted successfully.`) + } + else { + 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" } + } + + } + return {status:"Success"} + + } catch (error: unknown) { + console.log('error: ', error); + if (error instanceof Error) { + return { + status: error.message, + }; + } else { + return { + status: "An unexpected error occurred", + }; + } + } +}; +const normalizeType = (type: any): string => { + if (!type) return "string"; // default + if (typeof type === "string") { + switch (type.toLowerCase()) { + case "boolean": + return "boolean"; + case "number": + return "number"; + case "string": + return "string"; + case "object": + return "object"; + case "array": + return "array"; + case "date": + return "date"; + default: + return "string"; + } + } + + // Handle cases like [Number], Boolean, Object + if (Array.isArray(type)) return "array"; + if (type === Boolean) return "boolean"; + if (type === Number) return "number"; + if (type === String) return "string"; + if (type === Object) return "object"; + + return "string"; // fallback +}; \ No newline at end of file