diff --git a/src/entities/project.ts b/src/entities/project.ts index 5cb6bad43..81aa09209 100644 --- a/src/entities/project.ts +++ b/src/entities/project.ts @@ -51,6 +51,7 @@ import { getQfRoundTotalProjectsDonationsSum, } from '../repositories/qfRoundRepository'; import { EstimatedMatching } from '../types/qfTypes'; +import { Campaign } from './campaign'; // tslint:disable-next-line:no-var-requires const moment = require('moment'); @@ -101,7 +102,6 @@ export enum OrderField { QualityScore = 'qualityScore', Verified = 'verified', Reactions = 'totalReactions', - Traceable = 'traceCampaignId', Donations = 'totalDonations', TraceDonations = 'totalTraceDonations', AcceptGiv = 'givingBlocksId', @@ -390,6 +390,9 @@ export class Project extends BaseEntity { @Field({ nullable: true }) reaction?: Reaction; + @Field(type => [Campaign], { nullable: true }) + campaigns: Campaign[]; + // only projects with status active can be listed automatically static pendingReviewSince(maximumDaysForListing: Number) { const maxDaysForListing = moment() diff --git a/src/resolvers/projectResolver.test.ts b/src/resolvers/projectResolver.test.ts index 6d4b240a3..7e5e636a3 100644 --- a/src/resolvers/projectResolver.test.ts +++ b/src/resolvers/projectResolver.test.ts @@ -5387,6 +5387,92 @@ function projectBySlugTestCases() { assert.isTrue(project.projectPower.totalPower > 0); }); + it('should return projects including active campaigns', async () => { + const projectWithCampaign = await saveProjectDirectlyToDb({ + ...createProjectData(), + title: String(new Date().getTime()), + slug: String(new Date().getTime()), + }); + const projectWithoutCampaign = await saveProjectDirectlyToDb({ + ...createProjectData(), + title: String(new Date().getTime()), + slug: String(new Date().getTime()), + }); + + const campaign = await Campaign.create({ + isActive: true, + type: CampaignType.ManuallySelected, + slug: generateRandomString(), + title: 'title1', + description: 'description1', + photo: 'https://google.com', + relatedProjectsSlugs: [projectWithCampaign.slug as string], + order: 1, + }).save(); + const result = await axios.post(graphqlUrl, { + query: fetchProjectsBySlugQuery, + variables: { + slug: projectWithCampaign.slug, + }, + }); + + const project = result.data.data.projectBySlug; + assert.equal(Number(project.id), projectWithCampaign.id); + + assert.exists(project.campaigns); + assert.isNotEmpty(project.campaigns); + + const projectWithoutCampaignResult = await axios.post(graphqlUrl, { + query: fetchProjectsBySlugQuery, + variables: { + slug: projectWithoutCampaign.slug, + }, + }); + + const project2 = projectWithoutCampaignResult.data.data.projectBySlug; + assert.equal(Number(project2.id), projectWithoutCampaign.id); + + assert.isEmpty(project2.campaigns); + + await campaign.remove(); + }); + + it('should return projects including active campaigns, even when sent slug is in the slugHistory of project', async () => { + const projectWithCampaign = await saveProjectDirectlyToDb({ + ...createProjectData(), + title: String(new Date().getTime()), + slug: String(new Date().getTime()), + }); + const previousSlug = `${String(new Date().getTime())}-previous`; + projectWithCampaign.slugHistory = [previousSlug]; + await projectWithCampaign.save(); + + const campaign = await Campaign.create({ + isActive: true, + type: CampaignType.ManuallySelected, + slug: generateRandomString(), + title: 'title1', + description: 'description1', + photo: 'https://google.com', + relatedProjectsSlugs: [previousSlug], + order: 1, + }).save(); + const result = await axios.post(graphqlUrl, { + query: fetchProjectsBySlugQuery, + variables: { + slug: previousSlug, + }, + }); + + const project = result.data.data.projectBySlug; + assert.equal(Number(project.id), projectWithCampaign.id); + + assert.exists(project.campaigns); + assert.isNotEmpty(project.campaigns); + + await campaign.remove(); + }); + it('should return projects including project future power rank', async () => { await AppDataSource.getDataSource().query( 'truncate power_snapshot cascade', diff --git a/src/resolvers/projectResolver.ts b/src/resolvers/projectResolver.ts index 1d3029b09..c68c60214 100644 --- a/src/resolvers/projectResolver.ts +++ b/src/resolvers/projectResolver.ts @@ -486,11 +486,6 @@ export class ProjectResolver { return query.andWhere(`project.${filter} ${acceptGiv} NULL`); } - if (filter === 'traceCampaignId') { - const isRequested = filterValue ? 'IS NOT' : 'IS'; - return query.andWhere(`project.${filter} ${isRequested} NULL`); - } - if ( (filter === FilterField.AcceptFundOnGnosis || filter === FilterField.AcceptFundOnCelo || @@ -896,6 +891,15 @@ export class ProjectResolver { .leftJoinAndSelect('project.projectInstantPower', 'projectInstantPower') .leftJoinAndSelect('project.qfRounds', 'qfRounds') .leftJoinAndSelect('project.projectFuturePower', 'projectFuturePower') + .leftJoinAndMapMany( + 'project.campaigns', + Campaign, + 'campaigns', + '(campaigns."relatedProjectsSlugs" && ARRAY[:slug]::text[] OR campaigns."relatedProjectsSlugs" && project."slugHistory") AND campaigns."isActive" = TRUE', + { + slug, + }, + ) .leftJoin('project.adminUser', 'user') .addSelect(publicSelectionFields); // aliased selection diff --git a/test/graphqlQueries.ts b/test/graphqlQueries.ts index 247ef73fe..e041a1625 100644 --- a/test/graphqlQueries.ts +++ b/test/graphqlQueries.ts @@ -806,6 +806,24 @@ export const fetchProjectsBySlugQuery = ` listed reviewStatus givingBlocksId + campaigns { + id + title + description + type + photo + video + videoPreview + slug + isActive + order + landingLink + filterFields + sortingField + createdAt + updatedAt + + } givbackFactor projectPower { totalPower