From 487e3f1529ecf3f77f9ad6d78fa9dd7f5e57fd34 Mon Sep 17 00:00:00 2001 From: joonashak Date: Fri, 18 Oct 2024 23:27:43 +0300 Subject: [PATCH] Query to find connection graphs --- .../connection/connection-graph.resolver.ts | 15 ++++++ .../connection/connection-graph.service.ts | 53 +++++++++++++++++++ .../entities/connection/connection.module.ts | 11 +++- .../entities/connection/dto/chain-root.dto.ts | 12 +++++ .../dto/find-connection-graph.dto.ts | 11 ++++ .../connection/dto/graph-connection.dto.ts | 11 ++++ 6 files changed, 111 insertions(+), 2 deletions(-) create mode 100644 server/src/entities/connection/connection-graph.resolver.ts create mode 100644 server/src/entities/connection/connection-graph.service.ts create mode 100644 server/src/entities/connection/dto/chain-root.dto.ts create mode 100644 server/src/entities/connection/dto/find-connection-graph.dto.ts create mode 100644 server/src/entities/connection/dto/graph-connection.dto.ts diff --git a/server/src/entities/connection/connection-graph.resolver.ts b/server/src/entities/connection/connection-graph.resolver.ts new file mode 100644 index 00000000..76504f5b --- /dev/null +++ b/server/src/entities/connection/connection-graph.resolver.ts @@ -0,0 +1,15 @@ +import { Args, Query, Resolver } from "@nestjs/graphql"; +import { ConnectionGraphService } from "./connection-graph.service"; +import { FindConnectionGraph } from "./dto/find-connection-graph.dto"; + +@Resolver() +export class ConnectionGraphResolver { + constructor(private connectionGraphService: ConnectionGraphService) {} + + @Query(() => FindConnectionGraph) + async findConnectionGraph( + @Args("root") root: string, + ): Promise { + return this.connectionGraphService.findBySystem(root); + } +} diff --git a/server/src/entities/connection/connection-graph.service.ts b/server/src/entities/connection/connection-graph.service.ts new file mode 100644 index 00000000..8c97f310 --- /dev/null +++ b/server/src/entities/connection/connection-graph.service.ts @@ -0,0 +1,53 @@ +import { Injectable } from "@nestjs/common"; +import { InjectModel } from "@nestjs/mongoose"; +import { Model } from "mongoose"; +import { Connection } from "./connection.model"; +import { FindConnectionGraph } from "./dto/find-connection-graph.dto"; + +@Injectable() +export class ConnectionGraphService { + constructor( + @InjectModel(Connection.name) private connectionModel: Model, + ) {} + + async findBySystem(root: string): Promise { + const chains = await this.connectionModel.aggregate([ + { $match: { from: root } }, + { + $graphLookup: { + from: "connections", + startWith: "$to", + connectFromField: "to", + connectToField: "from", + as: "children", + depthField: "depth", + maxDepth: 100, + }, + }, + ]); + + return { + root, + // TODO: All children are repeated in each chain. Filter or return only one list of children? + chains: chains.map(this.addIdsToChain), + }; + } + + /** + * Mongo's default ID's (`_id`) need to be mapped manually to GraphQL `id` + * fields because Mongoose does not apply the model to aggregate pipeline + * results. + */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private addIdsToChain(chain: any) { + return { + ...chain, + id: chain._id, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + children: chain.children.map((child: any) => ({ + ...child, + id: child._id, + })), + }; + } +} diff --git a/server/src/entities/connection/connection.module.ts b/server/src/entities/connection/connection.module.ts index dd2d94d6..3e157b16 100644 --- a/server/src/entities/connection/connection.module.ts +++ b/server/src/entities/connection/connection.module.ts @@ -1,5 +1,7 @@ import { Module } from "@nestjs/common"; import { MongooseModule } from "@nestjs/mongoose"; +import { ConnectionGraphResolver } from "./connection-graph.resolver"; +import { ConnectionGraphService } from "./connection-graph.service"; import { Connection, ConnectionSchema } from "./connection.model"; import { ConnectionResolver } from "./connection.resolver"; import { ConnectionService } from "./connection.service"; @@ -10,7 +12,12 @@ import { ConnectionService } from "./connection.service"; { name: Connection.name, schema: ConnectionSchema }, ]), ], - providers: [ConnectionService, ConnectionResolver], - exports: [MongooseModule, ConnectionService], + providers: [ + ConnectionService, + ConnectionResolver, + ConnectionGraphService, + ConnectionGraphResolver, + ], + exports: [MongooseModule, ConnectionService, ConnectionGraphService], }) export class ConnectionModule {} diff --git a/server/src/entities/connection/dto/chain-root.dto.ts b/server/src/entities/connection/dto/chain-root.dto.ts new file mode 100644 index 00000000..99edf942 --- /dev/null +++ b/server/src/entities/connection/dto/chain-root.dto.ts @@ -0,0 +1,12 @@ +import { Field, ObjectType, OmitType } from "@nestjs/graphql"; +import { Connection } from "../connection.model"; +import { GraphConnection } from "./graph-connection.dto"; + +@ObjectType() +export class ChainRoot extends OmitType(Connection, ["reverse"]) { + @Field() + reverse: string; + + @Field(() => [GraphConnection]) + children: GraphConnection[]; +} diff --git a/server/src/entities/connection/dto/find-connection-graph.dto.ts b/server/src/entities/connection/dto/find-connection-graph.dto.ts new file mode 100644 index 00000000..bae95a53 --- /dev/null +++ b/server/src/entities/connection/dto/find-connection-graph.dto.ts @@ -0,0 +1,11 @@ +import { Field, ObjectType } from "@nestjs/graphql"; +import { ChainRoot } from "./chain-root.dto"; + +@ObjectType() +export class FindConnectionGraph { + @Field() + root: string; + + @Field(() => [ChainRoot]) + chains: ChainRoot[]; +} diff --git a/server/src/entities/connection/dto/graph-connection.dto.ts b/server/src/entities/connection/dto/graph-connection.dto.ts new file mode 100644 index 00000000..2ae3cc63 --- /dev/null +++ b/server/src/entities/connection/dto/graph-connection.dto.ts @@ -0,0 +1,11 @@ +import { Field, ObjectType, OmitType } from "@nestjs/graphql"; +import { Connection } from "../connection.model"; + +@ObjectType() +export class GraphConnection extends OmitType(Connection, ["reverse"]) { + @Field() + reverse: string; + + @Field() + depth: number; +}