diff --git a/src/entities/ProjectEstimatedMatchingView.ts b/src/entities/ProjectEstimatedMatchingView.ts index 73b209c0c..518e70ce9 100644 --- a/src/entities/ProjectEstimatedMatchingView.ts +++ b/src/entities/ProjectEstimatedMatchingView.ts @@ -1,4 +1,4 @@ -import { ObjectType } from 'type-graphql'; +import { Field, ObjectType } from 'type-graphql'; import { Entity, Column, @@ -8,6 +8,8 @@ import { ViewEntity, ManyToOne, RelationId, + ViewColumn, + JoinColumn, } from 'typeorm'; import { Project } from './project'; @@ -26,26 +28,39 @@ import { Project } from './project'; ]) @ObjectType() export class ProjectEstimatedMatchingView extends BaseEntity { + @Field(type => Project) + @ManyToOne(type => Project, project => project.projectEstimatedMatchingView) + @JoinColumn({ referencedColumnName: 'id' }) + project: Project; + + @Field() + @ViewColumn() @PrimaryColumn() projectId: number; // QF Round ID associated with the donations + @ViewColumn() + @Field() @PrimaryColumn() qfRoundId: number; // Sum of the square root of the value in USD of the donations + @ViewColumn() @Column('double precision') sqrtRootSum: number; // Count of unique donations per user per project per QF round + @ViewColumn() @Column('int') uniqueDonationCount: number; // Sum of the value in USD of the donations for active QF rounds where the donation status is verified + @ViewColumn() @Column('double precision') sumValueUsd: number; // Count of unique donors who have verified donations for each project + @ViewColumn() @Column('int') uniqueDonorsCount: number; } diff --git a/src/entities/project.ts b/src/entities/project.ts index 018486f36..a39f4714a 100644 --- a/src/entities/project.ts +++ b/src/entities/project.ts @@ -52,6 +52,7 @@ import { } from '../repositories/qfRoundRepository'; import { EstimatedMatching } from '../types/qfTypes'; import { Campaign } from './campaign'; +import { ProjectEstimatedMatchingView } from './ProjectEstimatedMatchingView'; // tslint:disable-next-line:no-var-requires const moment = require('moment'); @@ -342,6 +343,13 @@ export class Project extends BaseEntity { @OneToMany(type => SocialProfile, socialProfile => socialProfile.project) socialProfiles?: SocialProfile[]; + @Field(type => [ProjectEstimatedMatchingView], { nullable: true }) + @OneToMany( + type => ProjectEstimatedMatchingView, + projectEstimatedMatchingView => projectEstimatedMatchingView.project, + ) + projectEstimatedMatchingView?: ProjectEstimatedMatchingView[]; + @Field(type => Float) @Column({ type: 'real' }) totalDonations: number; diff --git a/src/repositories/projectRepository.ts b/src/repositories/projectRepository.ts index 3da2e8836..4f58c04bd 100644 --- a/src/repositories/projectRepository.ts +++ b/src/repositories/projectRepository.ts @@ -9,17 +9,10 @@ import { } from '../entities/project'; import { ProjectVerificationForm } from '../entities/projectVerificationForm'; import { ProjectAddress } from '../entities/projectAddress'; -import { - errorMessages, - i18n, - translationErrorMessagesKeys, -} from '../utils/errorMessages'; +import { i18n, translationErrorMessagesKeys } from '../utils/errorMessages'; import { User, publicSelectionFields } from '../entities/user'; import { ResourcesTotalPerMonthAndYear } from '../resolvers/donationResolver'; import { OrderDirection, ProjectResolver } from '../resolvers/projectResolver'; -import { ProjectEstimatedMatchingView } from '../entities/ProjectEstimatedMatchingView'; -import { findActiveQfRound } from './qfRoundRepository'; - export const findProjectById = (projectId: number): Promise => { // return Project.findOne({ id: projectId }); @@ -62,7 +55,6 @@ export type FilterProjectQueryInputParams = { sortingBy?: SortingField; qfRoundId?: number; activeQfRoundId?: number; - qfRoundProjectsIds?: number[]; }; export const filterProjectsQuery = (params: FilterProjectQueryInputParams) => { const { @@ -76,7 +68,6 @@ export const filterProjectsQuery = (params: FilterProjectQueryInputParams) => { slugArray, qfRoundId, activeQfRoundId, - qfRoundProjectsIds, } = params; let query = Project.createQueryBuilder('project') @@ -175,10 +166,24 @@ export const filterProjectsQuery = (params: FilterProjectQueryInputParams) => { ); break; case SortingField.ActiveQfRoundRaisedFunds: - if (activeQfRoundId && qfRoundProjectsIds) { - query.andWhere('project.id IN (:...qfRoundProjectsIds)', { - qfRoundProjectsIds, - }); + if (activeQfRoundId) { + query + .innerJoin( + 'project.projectEstimatedMatchingView', + 'projectEstimatedMatchingView', + ) + .addSelect([ + 'projectEstimatedMatchingView.sumValueUsd', + 'projectEstimatedMatchingView.qfRoundId', + ]) + .andWhere('projectEstimatedMatchingView.qfRoundId = :qfRoundId', { + qfRoundId: activeQfRoundId, + }) + .orderBy( + 'projectEstimatedMatchingView.sumValueUsd', + OrderDirection.DESC, + ) + .addOrderBy(`project.verified`, OrderDirection.DESC); } break; default: diff --git a/src/resolvers/projectResolver.ts b/src/resolvers/projectResolver.ts index 12d8fe296..edd87d55d 100644 --- a/src/resolvers/projectResolver.ts +++ b/src/resolvers/projectResolver.ts @@ -762,18 +762,9 @@ export class ProjectResolver { let projects: Project[]; let totalCount: number; let activeQfRoundId: number | undefined; - let qfRoundProjectsIds: number[] | undefined; if (sortingBy === SortingField.ActiveQfRoundRaisedFunds) { activeQfRoundId = (await findActiveQfRound())?.id; - - if (activeQfRoundId) { - qfRoundProjectsIds = await projectsInQfRoundOrderedBySumValueUsd( - activeQfRoundId, - limit, - skip, - ); - } } const filterQueryParams: FilterProjectQueryInputParams = { @@ -786,7 +777,6 @@ export class ProjectResolver { sortingBy, qfRoundId, activeQfRoundId, - qfRoundProjectsIds, }; let campaign; if (campaignSlug) { @@ -815,25 +805,6 @@ export class ProjectResolver { .cache(projectsQueryCacheKey, projectFiltersCacheDuration) .getManyAndCount(); - // No join or custom subquery worked, solution is to write a full raw sql which is too much work - // Sorting will be required to be done in-memory - if ( - sortingBy === SortingField.ActiveQfRoundRaisedFunds && - activeQfRoundId && - qfRoundProjectsIds - ) { - const orderMap: { [key: string]: number } = {}; - qfRoundProjectsIds.forEach((id, index) => { - orderMap[id] = index; - }); - - const orderedProjects = projects.sort((a, b) => { - return orderMap[a.id] - orderMap[b.id]; - }); - - projects = orderedProjects; - } - const userId = connectedWalletUserId || user?.userId; if (projects.length > 0 && userId) { const userReactions = await findUserReactionsByProjectIds(