From eb9cdf8bb0c5bcb9213b84a23b33d1cc75ca34c7 Mon Sep 17 00:00:00 2001 From: Cherik Date: Mon, 14 Oct 2024 18:11:14 +0330 Subject: [PATCH 01/17] init custom resolver --- src/server-extension/resolver.ts | 33 +++++++++++++++++++++++++ src/server-extension/resolvers/index.ts | 1 + src/server-extension/types.ts | 28 +++++++++++++++++++++ 3 files changed, 62 insertions(+) create mode 100644 src/server-extension/resolver.ts create mode 100644 src/server-extension/resolvers/index.ts create mode 100644 src/server-extension/types.ts diff --git a/src/server-extension/resolver.ts b/src/server-extension/resolver.ts new file mode 100644 index 0000000..5e9a784 --- /dev/null +++ b/src/server-extension/resolver.ts @@ -0,0 +1,33 @@ +import { Query, Resolver } from "type-graphql"; +import type { EntityManager } from "typeorm"; +import { Project } from "../model"; // This is the entity generated by Subsquid +import "reflect-metadata"; +import { ProjectType } from "./types"; + +@Resolver() +export class ProjectResolver { + constructor(private tx: () => Promise) {} + + @Query(() => [ProjectType]) + async getProjects(): Promise { + try { + const manager = await this.tx(); + const projects = await manager.getRepository(Project).find(); + + // Manually map the Project entity to the ProjectType + return projects.map((project) => ({ + id: project.id, + title: project.title as string, // Casting explicitly + description: project.description as string, + totalVouches: project.totalVouches, + totalFlags: project.totalFlags, + totalAttests: project.totalAttests, + url: project.url as string, + image: project.image as string, + })); + } catch (error) { + console.error("Error fetching projects:", error); + throw new Error("Failed to fetch projects"); + } + } +} diff --git a/src/server-extension/resolvers/index.ts b/src/server-extension/resolvers/index.ts new file mode 100644 index 0000000..c1b4b0a --- /dev/null +++ b/src/server-extension/resolvers/index.ts @@ -0,0 +1 @@ +export { ProjectResolver } from "../resolver"; diff --git a/src/server-extension/types.ts b/src/server-extension/types.ts new file mode 100644 index 0000000..979f0f6 --- /dev/null +++ b/src/server-extension/types.ts @@ -0,0 +1,28 @@ +import { Field, ID, Int, ObjectType } from "type-graphql"; + +@ObjectType() +export class ProjectType { + @Field(() => ID) + id!: string; + + @Field({ nullable: true }) // Allowing null or undefined + title?: string; + + @Field({ nullable: true }) + description?: string; + + @Field(() => Int) + totalVouches!: number; + + @Field(() => Int) + totalFlags!: number; + + @Field(() => Int) + totalAttests!: number; + + @Field({ nullable: true }) + url?: string; + + @Field({ nullable: true }) + image?: string; +} From 8ae4b2161675cc029f3941217346824f6327eb33 Mon Sep 17 00:00:00 2001 From: Cherik Date: Mon, 14 Oct 2024 18:49:13 +0330 Subject: [PATCH 02/17] add working query --- src/server-extension/resolver.ts | 58 +++++++++++++++++++++++--------- src/server-extension/types.ts | 36 +++++++++++--------- 2 files changed, 64 insertions(+), 30 deletions(-) diff --git a/src/server-extension/resolver.ts b/src/server-extension/resolver.ts index 5e9a784..ab185df 100644 --- a/src/server-extension/resolver.ts +++ b/src/server-extension/resolver.ts @@ -1,33 +1,61 @@ -import { Query, Resolver } from "type-graphql"; +import { Arg, Query, Resolver } from "type-graphql"; import type { EntityManager } from "typeorm"; -import { Project } from "../model"; // This is the entity generated by Subsquid +import { Project } from "../model"; +import { ProjectType } from "./types"; // Custom ProjectType import "reflect-metadata"; -import { ProjectType } from "./types"; @Resolver() export class ProjectResolver { constructor(private tx: () => Promise) {} @Query(() => [ProjectType]) - async getProjects(): Promise { + async getProjectsSortedByVouchOrFlag( + @Arg("orgIds", () => [String]) orgIds: string[], // Array of organization IDs + @Arg("sortBy", () => String) sortBy: "vouch" | "flag" // Sort by vouch or flag + ): Promise { try { const manager = await this.tx(); - const projects = await manager.getRepository(Project).find(); - // Manually map the Project entity to the ProjectType + // Determine whether we are sorting by vouches (true) or flags (false) + const vouchValue = sortBy === "vouch" ? true : false; + + // Query the projects vouched or flagged by the provided organizations + const projects = await manager + .getRepository(Project) + .createQueryBuilder("project") + .leftJoinAndSelect( + "project.attestedOrganisations", + "organisationProject" + ) + .leftJoinAndSelect("organisationProject.organisation", "organisation") + .where("organisation.id IN (:...orgIds)", { orgIds }) // Filter by array of organization IDs + .andWhere("organisationProject.vouch = :vouchValue", { vouchValue }) // Filter by vouch or flag + .groupBy("project.id") // Group by project ID to accumulate counts + .addGroupBy("project.title") // Group by project title as well + .addGroupBy("organisationProject.id") // Group by organisationProject ID + .addGroupBy("organisation.id") // Group by organisation ID + .addGroupBy("organisation.name") // Group by organisation name + .orderBy("SUM(organisationProject.count)", "DESC") // Order by the sum of counts + .getMany(); + + // Map the results to ProjectType return projects.map((project) => ({ id: project.id, - title: project.title as string, // Casting explicitly - description: project.description as string, - totalVouches: project.totalVouches, - totalFlags: project.totalFlags, - totalAttests: project.totalAttests, - url: project.url as string, - image: project.image as string, + title: project.title ?? "Untitled Project", // Handle null titles + attestedOrganisations: project.attestedOrganisations.map( + (orgProject) => ({ + vouch: orgProject.vouch, + count: orgProject.count, + organisation: { + id: orgProject.organisation.id, + name: orgProject.organisation.name, + }, + }) + ), })); } catch (error) { - console.error("Error fetching projects:", error); - throw new Error("Failed to fetch projects"); + console.error("Error fetching and sorting projects:", error); + throw new Error("Failed to fetch and sort projects"); } } } diff --git a/src/server-extension/types.ts b/src/server-extension/types.ts index 979f0f6..c4d1b04 100644 --- a/src/server-extension/types.ts +++ b/src/server-extension/types.ts @@ -1,28 +1,34 @@ -import { Field, ID, Int, ObjectType } from "type-graphql"; +import { Field, ObjectType, ID, Int } from "type-graphql"; @ObjectType() -export class ProjectType { +export class OrganisationType { @Field(() => ID) id!: string; - @Field({ nullable: true }) // Allowing null or undefined - title?: string; + @Field() + name!: string; +} - @Field({ nullable: true }) - description?: string; +@ObjectType() +export class OrganisationProjectType { + @Field(() => Boolean) + vouch!: boolean; @Field(() => Int) - totalVouches!: number; + count!: number; - @Field(() => Int) - totalFlags!: number; + @Field(() => OrganisationType) + organisation!: OrganisationType; +} - @Field(() => Int) - totalAttests!: number; +@ObjectType() +export class ProjectType { + @Field(() => ID) + id!: string; - @Field({ nullable: true }) - url?: string; + @Field(() => String, { nullable: true }) // Explicitly define the GraphQL type for title + title?: string | null; - @Field({ nullable: true }) - image?: string; + @Field(() => [OrganisationProjectType]) + attestedOrganisations!: OrganisationProjectType[]; } From 2afad01d312bfdb70e63ab44917d57b7e5118dfa Mon Sep 17 00:00:00 2001 From: Cherik Date: Mon, 14 Oct 2024 19:17:31 +0330 Subject: [PATCH 03/17] use raw query --- src/server-extension/resolver.ts | 74 ++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 32 deletions(-) diff --git a/src/server-extension/resolver.ts b/src/server-extension/resolver.ts index ab185df..3a1d239 100644 --- a/src/server-extension/resolver.ts +++ b/src/server-extension/resolver.ts @@ -1,8 +1,7 @@ +import "reflect-metadata"; import { Arg, Query, Resolver } from "type-graphql"; import type { EntityManager } from "typeorm"; -import { Project } from "../model"; import { ProjectType } from "./types"; // Custom ProjectType -import "reflect-metadata"; @Resolver() export class ProjectResolver { @@ -19,39 +18,50 @@ export class ProjectResolver { // Determine whether we are sorting by vouches (true) or flags (false) const vouchValue = sortBy === "vouch" ? true : false; - // Query the projects vouched or flagged by the provided organizations - const projects = await manager - .getRepository(Project) - .createQueryBuilder("project") - .leftJoinAndSelect( - "project.attestedOrganisations", - "organisationProject" - ) - .leftJoinAndSelect("organisationProject.organisation", "organisation") - .where("organisation.id IN (:...orgIds)", { orgIds }) // Filter by array of organization IDs - .andWhere("organisationProject.vouch = :vouchValue", { vouchValue }) // Filter by vouch or flag - .groupBy("project.id") // Group by project ID to accumulate counts - .addGroupBy("project.title") // Group by project title as well - .addGroupBy("organisationProject.id") // Group by organisationProject ID - .addGroupBy("organisation.id") // Group by organisation ID - .addGroupBy("organisation.name") // Group by organisation name - .orderBy("SUM(organisationProject.count)", "DESC") // Order by the sum of counts - .getMany(); + console.log("orgIds", orgIds); + + // Create raw SQL query + const query = ` + SELECT + project.id AS project_id, + project.title AS project_title, + organisation_project.id AS org_project_id, + organisation.id AS org_id, + organisation.name AS org_name, + SUM(organisation_project.count) AS total_count + FROM + project + LEFT JOIN organisation_project + ON project.id = organisation_project.project_id + LEFT JOIN organisation + ON organisation_project.organisation_id = organisation.id + WHERE + organisation.id IN (${orgIds.map((orgId) => `'${orgId}'`).join(",")}) + AND organisation_project.vouch = ${vouchValue} + GROUP BY + project.id, + organisation_project.id, + organisation.id + ORDER BY + SUM(organisation_project.count) DESC`; + + // Execute the query and pass in the organization IDs and vouchValue as parameters + const rawProjects = await manager.query(query); - // Map the results to ProjectType - return projects.map((project) => ({ - id: project.id, - title: project.title ?? "Untitled Project", // Handle null titles - attestedOrganisations: project.attestedOrganisations.map( - (orgProject) => ({ - vouch: orgProject.vouch, - count: orgProject.count, + // Map raw SQL result into ProjectType + return rawProjects.map((row: any) => ({ + id: row.project_id, + title: row.project_title ?? "Untitled Project", // Handle null titles + attestedOrganisations: [ + { + vouch: vouchValue, + count: row.total_count, organisation: { - id: orgProject.organisation.id, - name: orgProject.organisation.name, + id: row.org_id, + name: row.org_name, }, - }) - ), + }, + ], })); } catch (error) { console.error("Error fetching and sorting projects:", error); From 79d46c1a473ceca55f00468c5d9fe483e729f1ef Mon Sep 17 00:00:00 2001 From: Cherik Date: Mon, 14 Oct 2024 19:36:48 +0330 Subject: [PATCH 04/17] add getSelectedFields --- src/server-extension/helper.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/server-extension/helper.ts diff --git a/src/server-extension/helper.ts b/src/server-extension/helper.ts new file mode 100644 index 0000000..cade771 --- /dev/null +++ b/src/server-extension/helper.ts @@ -0,0 +1,16 @@ +import { GraphQLResolveInfo } from "graphql"; + +export function getSelectedFields(info: GraphQLResolveInfo): string[] { + const fields = info.fieldNodes[0].selectionSet?.selections; + const selectedFields: string[] = []; + + if (fields) { + fields.forEach((field: any) => { + if (field.name) { + selectedFields.push(`project.${field.name.value}`); + } + }); + } + + return selectedFields; +} From f3cce58a3cfa0dc1918ee7dfbcbddc17033c5399 Mon Sep 17 00:00:00 2001 From: Cherik Date: Mon, 14 Oct 2024 19:37:03 +0330 Subject: [PATCH 05/17] add info --- src/server-extension/resolver.ts | 51 +++++++++++++++++--------------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/src/server-extension/resolver.ts b/src/server-extension/resolver.ts index 3a1d239..da48c88 100644 --- a/src/server-extension/resolver.ts +++ b/src/server-extension/resolver.ts @@ -1,7 +1,9 @@ import "reflect-metadata"; -import { Arg, Query, Resolver } from "type-graphql"; +import { GraphQLResolveInfo } from "graphql"; +import { Arg, Info, Query, Resolver } from "type-graphql"; import type { EntityManager } from "typeorm"; import { ProjectType } from "./types"; // Custom ProjectType +import { getSelectedFields } from "./helper"; @Resolver() export class ProjectResolver { @@ -10,7 +12,8 @@ export class ProjectResolver { @Query(() => [ProjectType]) async getProjectsSortedByVouchOrFlag( @Arg("orgIds", () => [String]) orgIds: string[], // Array of organization IDs - @Arg("sortBy", () => String) sortBy: "vouch" | "flag" // Sort by vouch or flag + @Arg("sortBy", () => String) sortBy: "vouch" | "flag", // Sort by vouch or flag + @Info() info: GraphQLResolveInfo ): Promise { try { const manager = await this.tx(); @@ -20,30 +23,30 @@ export class ProjectResolver { console.log("orgIds", orgIds); + const selectedFields = getSelectedFields(info); + const fields = selectedFields.join(", "); + // Create raw SQL query const query = ` - SELECT - project.id AS project_id, - project.title AS project_title, - organisation_project.id AS org_project_id, - organisation.id AS org_id, - organisation.name AS org_name, - SUM(organisation_project.count) AS total_count - FROM - project - LEFT JOIN organisation_project - ON project.id = organisation_project.project_id - LEFT JOIN organisation - ON organisation_project.organisation_id = organisation.id - WHERE - organisation.id IN (${orgIds.map((orgId) => `'${orgId}'`).join(",")}) - AND organisation_project.vouch = ${vouchValue} - GROUP BY - project.id, - organisation_project.id, - organisation.id - ORDER BY - SUM(organisation_project.count) DESC`; + SELECT + ${fields}, + SUM(organisation_project.count) AS total_count + FROM + project + LEFT JOIN organisation_project + ON project.id = organisation_project.project_id + LEFT JOIN organisation + ON organisation_project.organisation_id = organisation.id + WHERE + organisation.id IN (${orgIds.map((orgId) => `'${orgId}'`).join(",")}) + AND organisation_project.vouch = ${vouchValue} + GROUP BY + project.id, + organisation_project.id, + organisation.id + ORDER BY + SUM(organisation_project.count) DESC; + `; // Execute the query and pass in the organization IDs and vouchValue as parameters const rawProjects = await manager.query(query); From abcd3ed47f45cd05f1e73175668bce731292b016 Mon Sep 17 00:00:00 2001 From: Cherik Date: Mon, 14 Oct 2024 19:44:44 +0330 Subject: [PATCH 06/17] make code clear --- src/server-extension/resolver.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/server-extension/resolver.ts b/src/server-extension/resolver.ts index da48c88..c05ad36 100644 --- a/src/server-extension/resolver.ts +++ b/src/server-extension/resolver.ts @@ -11,14 +11,13 @@ export class ProjectResolver { @Query(() => [ProjectType]) async getProjectsSortedByVouchOrFlag( - @Arg("orgIds", () => [String]) orgIds: string[], // Array of organization IDs - @Arg("sortBy", () => String) sortBy: "vouch" | "flag", // Sort by vouch or flag + @Arg("orgIds", () => [String]) orgIds: string[], + @Arg("sortBy", () => String) sortBy: "vouch" | "flag", @Info() info: GraphQLResolveInfo ): Promise { try { const manager = await this.tx(); - // Determine whether we are sorting by vouches (true) or flags (false) const vouchValue = sortBy === "vouch" ? true : false; console.log("orgIds", orgIds); @@ -26,7 +25,6 @@ export class ProjectResolver { const selectedFields = getSelectedFields(info); const fields = selectedFields.join(", "); - // Create raw SQL query const query = ` SELECT ${fields}, @@ -48,13 +46,11 @@ export class ProjectResolver { SUM(organisation_project.count) DESC; `; - // Execute the query and pass in the organization IDs and vouchValue as parameters const rawProjects = await manager.query(query); - // Map raw SQL result into ProjectType return rawProjects.map((row: any) => ({ id: row.project_id, - title: row.project_title ?? "Untitled Project", // Handle null titles + title: row.project_title ?? "Untitled Project", attestedOrganisations: [ { vouch: vouchValue, From 9ab3c7ab4170d4cabaf674ee7086eda1b74c9316 Mon Sep 17 00:00:00 2001 From: Cherik Date: Mon, 14 Oct 2024 20:12:54 +0330 Subject: [PATCH 07/17] remove map --- src/server-extension/resolver.ts | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/src/server-extension/resolver.ts b/src/server-extension/resolver.ts index c05ad36..c771e7c 100644 --- a/src/server-extension/resolver.ts +++ b/src/server-extension/resolver.ts @@ -20,9 +20,8 @@ export class ProjectResolver { const vouchValue = sortBy === "vouch" ? true : false; - console.log("orgIds", orgIds); - const selectedFields = getSelectedFields(info); + console.log("selectedFields", selectedFields); const fields = selectedFields.join(", "); const query = ` @@ -48,20 +47,7 @@ export class ProjectResolver { const rawProjects = await manager.query(query); - return rawProjects.map((row: any) => ({ - id: row.project_id, - title: row.project_title ?? "Untitled Project", - attestedOrganisations: [ - { - vouch: vouchValue, - count: row.total_count, - organisation: { - id: row.org_id, - name: row.org_name, - }, - }, - ], - })); + return rawProjects; } catch (error) { console.error("Error fetching and sorting projects:", error); throw new Error("Failed to fetch and sort projects"); From f7015a7009bcf53959224a23042cb0f2a65f730c Mon Sep 17 00:00:00 2001 From: Cherik Date: Mon, 14 Oct 2024 20:15:40 +0330 Subject: [PATCH 08/17] add limit and offset --- src/server-extension/resolver.ts | 39 +++++++++++++++++--------------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/src/server-extension/resolver.ts b/src/server-extension/resolver.ts index c771e7c..1edddb4 100644 --- a/src/server-extension/resolver.ts +++ b/src/server-extension/resolver.ts @@ -13,6 +13,8 @@ export class ProjectResolver { async getProjectsSortedByVouchOrFlag( @Arg("orgIds", () => [String]) orgIds: string[], @Arg("sortBy", () => String) sortBy: "vouch" | "flag", + @Arg("limit", () => Number, { nullable: true }) limit: number = 10, + @Arg("offset", () => Number, { nullable: true }) offset: number = 0, @Info() info: GraphQLResolveInfo ): Promise { try { @@ -25,24 +27,25 @@ export class ProjectResolver { const fields = selectedFields.join(", "); const query = ` - SELECT - ${fields}, - SUM(organisation_project.count) AS total_count - FROM - project - LEFT JOIN organisation_project - ON project.id = organisation_project.project_id - LEFT JOIN organisation - ON organisation_project.organisation_id = organisation.id - WHERE - organisation.id IN (${orgIds.map((orgId) => `'${orgId}'`).join(",")}) - AND organisation_project.vouch = ${vouchValue} - GROUP BY - project.id, - organisation_project.id, - organisation.id - ORDER BY - SUM(organisation_project.count) DESC; + SELECT + ${fields}, + SUM(organisation_project.count) AS total_count + FROM + project + LEFT JOIN organisation_project + ON project.id = organisation_project.project_id + LEFT JOIN organisation + ON organisation_project.organisation_id = organisation.id + WHERE + organisation.id IN (${orgIds.map((orgId) => `'${orgId}'`).join(",")}) + AND organisation_project.vouch = ${vouchValue} + GROUP BY + project.id, + organisation_project.id, + organisation.id + ORDER BY + SUM(organisation_project.count) DESC + LIMIT ${limit} OFFSET ${offset}; `; const rawProjects = await manager.query(query); From b3f877590a6f499ef3518183182d579d2c1a7871 Mon Sep 17 00:00:00 2001 From: Cherik Date: Mon, 14 Oct 2024 20:16:54 +0330 Subject: [PATCH 09/17] remove logs --- src/server-extension/resolver.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/server-extension/resolver.ts b/src/server-extension/resolver.ts index 1edddb4..a068cd1 100644 --- a/src/server-extension/resolver.ts +++ b/src/server-extension/resolver.ts @@ -23,7 +23,6 @@ export class ProjectResolver { const vouchValue = sortBy === "vouch" ? true : false; const selectedFields = getSelectedFields(info); - console.log("selectedFields", selectedFields); const fields = selectedFields.join(", "); const query = ` From 308731d47b682af4a65987d05856309c570765ac Mon Sep 17 00:00:00 2001 From: Cherik Date: Mon, 14 Oct 2024 20:18:11 +0330 Subject: [PATCH 10/17] disable attestedOrganisations --- src/server-extension/types.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/server-extension/types.ts b/src/server-extension/types.ts index c4d1b04..7a5b0a4 100644 --- a/src/server-extension/types.ts +++ b/src/server-extension/types.ts @@ -29,6 +29,6 @@ export class ProjectType { @Field(() => String, { nullable: true }) // Explicitly define the GraphQL type for title title?: string | null; - @Field(() => [OrganisationProjectType]) - attestedOrganisations!: OrganisationProjectType[]; + // @Field(() => [OrganisationProjectType]) + // attestedOrganisations!: OrganisationProjectType[]; } From ab3105b730dca082631cfc8712082d6e6976d84c Mon Sep 17 00:00:00 2001 From: Cherik Date: Tue, 15 Oct 2024 17:33:04 +0330 Subject: [PATCH 11/17] limit to id --- src/server-extension/helper.ts | 16 ---------------- src/server-extension/resolver.ts | 8 ++------ 2 files changed, 2 insertions(+), 22 deletions(-) delete mode 100644 src/server-extension/helper.ts diff --git a/src/server-extension/helper.ts b/src/server-extension/helper.ts deleted file mode 100644 index cade771..0000000 --- a/src/server-extension/helper.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { GraphQLResolveInfo } from "graphql"; - -export function getSelectedFields(info: GraphQLResolveInfo): string[] { - const fields = info.fieldNodes[0].selectionSet?.selections; - const selectedFields: string[] = []; - - if (fields) { - fields.forEach((field: any) => { - if (field.name) { - selectedFields.push(`project.${field.name.value}`); - } - }); - } - - return selectedFields; -} diff --git a/src/server-extension/resolver.ts b/src/server-extension/resolver.ts index a068cd1..e450c0b 100644 --- a/src/server-extension/resolver.ts +++ b/src/server-extension/resolver.ts @@ -3,7 +3,6 @@ import { GraphQLResolveInfo } from "graphql"; import { Arg, Info, Query, Resolver } from "type-graphql"; import type { EntityManager } from "typeorm"; import { ProjectType } from "./types"; // Custom ProjectType -import { getSelectedFields } from "./helper"; @Resolver() export class ProjectResolver { @@ -20,14 +19,11 @@ export class ProjectResolver { try { const manager = await this.tx(); - const vouchValue = sortBy === "vouch" ? true : false; - - const selectedFields = getSelectedFields(info); - const fields = selectedFields.join(", "); + const vouchValue = sortBy === "vouch"; const query = ` SELECT - ${fields}, + project.id, SUM(organisation_project.count) AS total_count FROM project From 523fa9d8a5b1d1bb6b3d27b9508360ee58b0c6cd Mon Sep 17 00:00:00 2001 From: Cherik Date: Tue, 15 Oct 2024 17:35:08 +0330 Subject: [PATCH 12/17] update the type --- src/server-extension/resolver.ts | 6 +++--- src/server-extension/types.ts | 29 +---------------------------- 2 files changed, 4 insertions(+), 31 deletions(-) diff --git a/src/server-extension/resolver.ts b/src/server-extension/resolver.ts index e450c0b..5648976 100644 --- a/src/server-extension/resolver.ts +++ b/src/server-extension/resolver.ts @@ -2,20 +2,20 @@ import "reflect-metadata"; import { GraphQLResolveInfo } from "graphql"; import { Arg, Info, Query, Resolver } from "type-graphql"; import type { EntityManager } from "typeorm"; -import { ProjectType } from "./types"; // Custom ProjectType +import { ProjectsSortedByVouchOrFlagType } from "./types"; // Custom ProjectType @Resolver() export class ProjectResolver { constructor(private tx: () => Promise) {} - @Query(() => [ProjectType]) + @Query(() => [ProjectsSortedByVouchOrFlagType]) async getProjectsSortedByVouchOrFlag( @Arg("orgIds", () => [String]) orgIds: string[], @Arg("sortBy", () => String) sortBy: "vouch" | "flag", @Arg("limit", () => Number, { nullable: true }) limit: number = 10, @Arg("offset", () => Number, { nullable: true }) offset: number = 0, @Info() info: GraphQLResolveInfo - ): Promise { + ): Promise { try { const manager = await this.tx(); diff --git a/src/server-extension/types.ts b/src/server-extension/types.ts index 7a5b0a4..73ad734 100644 --- a/src/server-extension/types.ts +++ b/src/server-extension/types.ts @@ -1,34 +1,7 @@ import { Field, ObjectType, ID, Int } from "type-graphql"; @ObjectType() -export class OrganisationType { +export class ProjectsSortedByVouchOrFlagType { @Field(() => ID) id!: string; - - @Field() - name!: string; -} - -@ObjectType() -export class OrganisationProjectType { - @Field(() => Boolean) - vouch!: boolean; - - @Field(() => Int) - count!: number; - - @Field(() => OrganisationType) - organisation!: OrganisationType; -} - -@ObjectType() -export class ProjectType { - @Field(() => ID) - id!: string; - - @Field(() => String, { nullable: true }) // Explicitly define the GraphQL type for title - title?: string | null; - - // @Field(() => [OrganisationProjectType]) - // attestedOrganisations!: OrganisationProjectType[]; } From 16900f99db8a4b2d7a23dff224622c4f061545bb Mon Sep 17 00:00:00 2001 From: Cherik Date: Tue, 15 Oct 2024 17:49:26 +0330 Subject: [PATCH 13/17] only group by project id --- src/server-extension/resolver.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/server-extension/resolver.ts b/src/server-extension/resolver.ts index 5648976..8e7f06c 100644 --- a/src/server-extension/resolver.ts +++ b/src/server-extension/resolver.ts @@ -35,9 +35,7 @@ export class ProjectResolver { organisation.id IN (${orgIds.map((orgId) => `'${orgId}'`).join(",")}) AND organisation_project.vouch = ${vouchValue} GROUP BY - project.id, - organisation_project.id, - organisation.id + project.id ORDER BY SUM(organisation_project.count) DESC LIMIT ${limit} OFFSET ${offset}; From ffc7b7e918164c64c46af38d403a793a4908909c Mon Sep 17 00:00:00 2001 From: Cherik Date: Tue, 15 Oct 2024 18:03:39 +0330 Subject: [PATCH 14/17] use Parameterized Query --- src/server-extension/resolver.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/server-extension/resolver.ts b/src/server-extension/resolver.ts index 8e7f06c..43ee3fb 100644 --- a/src/server-extension/resolver.ts +++ b/src/server-extension/resolver.ts @@ -32,16 +32,21 @@ export class ProjectResolver { LEFT JOIN organisation ON organisation_project.organisation_id = organisation.id WHERE - organisation.id IN (${orgIds.map((orgId) => `'${orgId}'`).join(",")}) - AND organisation_project.vouch = ${vouchValue} + organisation.id = ANY($1) + AND organisation_project.vouch = $2 GROUP BY project.id ORDER BY SUM(organisation_project.count) DESC - LIMIT ${limit} OFFSET ${offset}; - `; + LIMIT $3 OFFSET $4; + `; - const rawProjects = await manager.query(query); + const rawProjects = await manager.query(query, [ + orgIds, + vouchValue, + limit, + offset, + ]); return rawProjects; } catch (error) { From b2344add109d3f2e06489494ed0e4e87a5418a30 Mon Sep 17 00:00:00 2001 From: Cherik Date: Tue, 15 Oct 2024 18:31:08 +0330 Subject: [PATCH 15/17] handle sortBy --- src/server-extension/resolver.ts | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/src/server-extension/resolver.ts b/src/server-extension/resolver.ts index 43ee3fb..db2b9af 100644 --- a/src/server-extension/resolver.ts +++ b/src/server-extension/resolver.ts @@ -4,6 +4,28 @@ import { Arg, Info, Query, Resolver } from "type-graphql"; import type { EntityManager } from "typeorm"; import { ProjectsSortedByVouchOrFlagType } from "./types"; // Custom ProjectType +enum EProjectSort { + HIGHEST_VOUCH_COUNT, + LOWEST_VOUCH_COUNT, + HIGHEST_FLAG, + LOWEST_FLAG, +} + +const getSortBy = (sortBy: EProjectSort) => { + switch (sortBy) { + case EProjectSort.HIGHEST_VOUCH_COUNT: + return { sortBy: "vouch", order: "DESC" }; + case EProjectSort.LOWEST_VOUCH_COUNT: + return { sortBy: "vouch", order: "ASC" }; + case EProjectSort.HIGHEST_FLAG: + return { sortBy: "flag", order: "DESC" }; + case EProjectSort.LOWEST_FLAG: + return { sortBy: "flag", order: "ASC" }; + default: + return { sortBy: "vouch", order: "DESC" }; + } +}; + @Resolver() export class ProjectResolver { constructor(private tx: () => Promise) {} @@ -11,7 +33,7 @@ export class ProjectResolver { @Query(() => [ProjectsSortedByVouchOrFlagType]) async getProjectsSortedByVouchOrFlag( @Arg("orgIds", () => [String]) orgIds: string[], - @Arg("sortBy", () => String) sortBy: "vouch" | "flag", + @Arg("sortBy", () => String) sortBy: EProjectSort, @Arg("limit", () => Number, { nullable: true }) limit: number = 10, @Arg("offset", () => Number, { nullable: true }) offset: number = 0, @Info() info: GraphQLResolveInfo @@ -19,7 +41,9 @@ export class ProjectResolver { try { const manager = await this.tx(); - const vouchValue = sortBy === "vouch"; + const sortInfo = getSortBy(sortBy); + + const vouchValue = sortInfo.sortBy === "vouch"; const query = ` SELECT @@ -37,7 +61,7 @@ export class ProjectResolver { GROUP BY project.id ORDER BY - SUM(organisation_project.count) DESC + SUM(organisation_project.count) ${sortInfo.order} LIMIT $3 OFFSET $4; `; From 6908b75561b02cbf71cf7bd33ff0e99cb659fafe Mon Sep 17 00:00:00 2001 From: Cherik Date: Tue, 15 Oct 2024 18:37:04 +0330 Subject: [PATCH 16/17] add value to EProjectSort enum --- src/server-extension/resolver.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/server-extension/resolver.ts b/src/server-extension/resolver.ts index db2b9af..eeb33d6 100644 --- a/src/server-extension/resolver.ts +++ b/src/server-extension/resolver.ts @@ -5,10 +5,10 @@ import type { EntityManager } from "typeorm"; import { ProjectsSortedByVouchOrFlagType } from "./types"; // Custom ProjectType enum EProjectSort { - HIGHEST_VOUCH_COUNT, - LOWEST_VOUCH_COUNT, - HIGHEST_FLAG, - LOWEST_FLAG, + HIGHEST_VOUCH_COUNT = "totalVouches_DESC", + LOWEST_VOUCH_COUNT = "totalVouches_ASC", + HIGHEST_FLAG = "totalFlags_DESC", + LOWEST_FLAG = "totalFlags_ASC", } const getSortBy = (sortBy: EProjectSort) => { From f9a1c1c7a42eba9db90e11b42dfa092c5c097034 Mon Sep 17 00:00:00 2001 From: Cherik Date: Tue, 15 Oct 2024 18:39:54 +0330 Subject: [PATCH 17/17] move to proper files --- src/server-extension/helper.ts | 16 ++++++++++++++++ src/server-extension/resolver.ts | 27 +++------------------------ src/server-extension/types.ts | 6 ++++++ 3 files changed, 25 insertions(+), 24 deletions(-) create mode 100644 src/server-extension/helper.ts diff --git a/src/server-extension/helper.ts b/src/server-extension/helper.ts new file mode 100644 index 0000000..08b485f --- /dev/null +++ b/src/server-extension/helper.ts @@ -0,0 +1,16 @@ +import { EProjectSort } from "./types"; + +export const getProjectSortBy = (sortBy: EProjectSort) => { + switch (sortBy) { + case EProjectSort.HIGHEST_VOUCH_COUNT: + return { sortBy: "vouch", order: "DESC" }; + case EProjectSort.LOWEST_VOUCH_COUNT: + return { sortBy: "vouch", order: "ASC" }; + case EProjectSort.HIGHEST_FLAG: + return { sortBy: "flag", order: "DESC" }; + case EProjectSort.LOWEST_FLAG: + return { sortBy: "flag", order: "ASC" }; + default: + return { sortBy: "vouch", order: "DESC" }; + } +}; diff --git a/src/server-extension/resolver.ts b/src/server-extension/resolver.ts index eeb33d6..469ff3d 100644 --- a/src/server-extension/resolver.ts +++ b/src/server-extension/resolver.ts @@ -2,29 +2,8 @@ import "reflect-metadata"; import { GraphQLResolveInfo } from "graphql"; import { Arg, Info, Query, Resolver } from "type-graphql"; import type { EntityManager } from "typeorm"; -import { ProjectsSortedByVouchOrFlagType } from "./types"; // Custom ProjectType - -enum EProjectSort { - HIGHEST_VOUCH_COUNT = "totalVouches_DESC", - LOWEST_VOUCH_COUNT = "totalVouches_ASC", - HIGHEST_FLAG = "totalFlags_DESC", - LOWEST_FLAG = "totalFlags_ASC", -} - -const getSortBy = (sortBy: EProjectSort) => { - switch (sortBy) { - case EProjectSort.HIGHEST_VOUCH_COUNT: - return { sortBy: "vouch", order: "DESC" }; - case EProjectSort.LOWEST_VOUCH_COUNT: - return { sortBy: "vouch", order: "ASC" }; - case EProjectSort.HIGHEST_FLAG: - return { sortBy: "flag", order: "DESC" }; - case EProjectSort.LOWEST_FLAG: - return { sortBy: "flag", order: "ASC" }; - default: - return { sortBy: "vouch", order: "DESC" }; - } -}; +import { EProjectSort, ProjectsSortedByVouchOrFlagType } from "./types"; // Custom ProjectType +import { getProjectSortBy } from "./helper"; @Resolver() export class ProjectResolver { @@ -41,7 +20,7 @@ export class ProjectResolver { try { const manager = await this.tx(); - const sortInfo = getSortBy(sortBy); + const sortInfo = getProjectSortBy(sortBy); const vouchValue = sortInfo.sortBy === "vouch"; diff --git a/src/server-extension/types.ts b/src/server-extension/types.ts index 73ad734..eb2e2ce 100644 --- a/src/server-extension/types.ts +++ b/src/server-extension/types.ts @@ -1,5 +1,11 @@ import { Field, ObjectType, ID, Int } from "type-graphql"; +export enum EProjectSort { + HIGHEST_VOUCH_COUNT = "totalVouches_DESC", + LOWEST_VOUCH_COUNT = "totalVouches_ASC", + HIGHEST_FLAG = "totalFlags_DESC", + LOWEST_FLAG = "totalFlags_ASC", +} @ObjectType() export class ProjectsSortedByVouchOrFlagType { @Field(() => ID)