Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 11 additions & 8 deletions src/dataLoaders/ProjectsDataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,14 +173,17 @@ export default class ProjectsDataSource {
ids: RepoDriverId[],
chain: DbSchema,
): Promise<ProjectDataValues[]> {
return (
await (this._batchProjectsByIds.loadMany(
ids.map((id) => ({
accountId: id,
chains: [chain],
})),
) as Promise<ProjectDataValues[]>)
).filter((p) => p.chain === chain);
const results = await this._batchProjectsByIds.loadMany(
ids.map((id) => ({
accountId: id,
chains: [chain],
})),
);

// Filter out errors and undefined values before accessing properties
return results
.filter((p): p is ProjectDataValues => p != null && !(p instanceof Error))
.filter((p) => p.chain === chain);
}

public async getEarnedFunds(
Expand Down
16 changes: 16 additions & 0 deletions src/dataLoaders/SupportDataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,22 @@ export default class SupportDataSource {
).filter((support) => support.chain === chain);
}

public async getOneTimeDonationSupportByAccountIdsOnChain(
accountIds: AccountId[],
chain: DbSchema,
): Promise<GivenEventModelDataValues[]> {
const results = await Promise.all(
accountIds.map((accountId) =>
this._batchOneTimeDonationSupportByAccountIds.load({
accountId,
chains: [chain],
}),
),
);

return results.flat().filter((support) => support.chain === chain);
}

public async getStreamSupportByAccountIdOnChain(
accountId: AccountId,
chain: DbSchema,
Expand Down
47 changes: 41 additions & 6 deletions src/ecosystem/ecosystemResolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ import assert, {
assertIsRepoDriverId,
assertMany,
isNftDriverId,
isRepoSubAccountDriverId,
} from '../utils/assert';
import { calcParentRepoDriverId } from '../utils/repoSubAccountIdUtils';
import { resolveTotalEarned } from '../common/commonResolverLogic';
import { chainToDbSchema } from '../utils/chainSchemaMappings';
import { getLatestMetadataHashOnChain } from '../utils/getLatestAccountMetadata';
Expand Down Expand Up @@ -128,10 +130,27 @@ const ecosystemResolvers = {
const linkedIdentityReceivers =
splitReceiversByReceiverAccountType.get('linked_identity') || [];

const projectIds =
projectReceivers.length > 0
? (projectReceivers.map((r) => r.receiverAccountId) as RepoDriverId[]) // Events processors ensure that all project IDs are RepoDriverIds.
: [];
// Get parent project IDs - transform sub-account IDs to parent IDs
// Detect ID type instead of relying solely on splitsToRepoDriverSubAccount flag
// Create a map to cache transformations and avoid duplicate async calls
const receiverToParentIdMap = new Map<string, RepoDriverId>();

if (projectReceivers.length > 0) {
await Promise.all(
projectReceivers.map(async (r) => {
const parentId = isRepoSubAccountDriverId(r.receiverAccountId)
? await calcParentRepoDriverId(
r.receiverAccountId,
ecosystemChain,
)
: (r.receiverAccountId as RepoDriverId);
receiverToParentIdMap.set(r.receiverAccountId, parentId);
}),
);
}

// Deduplicate project IDs (multiple sub-accounts can share the same parent)
const projectIds = Array.from(new Set(receiverToParentIdMap.values()));

const [projects, subLists] = await Promise.all([
projectReceivers.length > 0
Expand Down Expand Up @@ -163,9 +182,25 @@ const ecosystemResolvers = {

const projectDependencies = await Promise.all(
projectReceivers.map(async (s) => {
assertIsRepoDriverId(s.receiverAccountId);
// Detect the ID type to determine if this is a sub-account
const isSubAccount = isRepoSubAccountDriverId(s.receiverAccountId);

// When the ID is a RepoSubAccountDriver ID, we need to get the parent project ID
// Otherwise, the receiver ID is already the project ID
if (!isSubAccount) {
assertIsRepoDriverId(s.receiverAccountId);
}

// Get the parent project ID from the cached map (already calculated above)
const projectId = receiverToParentIdMap.get(s.receiverAccountId);

if (!projectId) {
return shouldNeverHappen(
`Expected parent ID to be cached for ${s.receiverAccountId}`,
);
}

const project = projectsMap.get(s.receiverAccountId);
const project = projectsMap.get(projectId);

return {
...s,
Expand Down
49 changes: 27 additions & 22 deletions src/project/projectResolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
assertMany,
isGitHubUrl,
isRepoDriverId,
isRepoSubAccountDriverId,
} from '../utils/assert';
import { resolveTotalEarned } from '../common/commonResolverLogic';
import { validateChainsQueryArg } from '../utils/commonInputValidators';
Expand All @@ -48,6 +49,10 @@ import { calcSubRepoDriverId } from '../utils/repoSubAccountIdUtils';
import toGqlLinkedIdentity from '../linked-identity/linkedIdentityUtils';
import { getGitHubRepoByUrl } from './github';
import { PUBLIC_ERROR_CODES } from '../utils/formatError';
import {
getProjectSplitSupport,
getProjectOneTimeDonationSupport,
} from './projectSupportHelpers';

const projectResolvers = {
Query: {
Expand Down Expand Up @@ -83,7 +88,8 @@ const projectResolvers = {
{ id, chains }: { id: RepoDriverId; chains?: SupportedChain[] },
{ dataSources: { projectsDataSource } }: Context,
): Promise<ResolverProject | null> => {
if (!isRepoDriverId(id)) {
// Accept both repo driver IDs and repo sub-account driver IDs
if (!isRepoDriverId(id) && !isRepoSubAccountDriverId(id)) {
return null;
}

Expand Down Expand Up @@ -396,11 +402,11 @@ const projectResolvers = {
},
}: Context,
) => {
const splitReceivers =
await supportDataSource.getSplitSupportByReceiverIdOnChain(
projectId,
projectChain,
);
const splitReceivers = await getProjectSplitSupport(
projectId,
projectChain,
supportDataSource,
);

const supportItems = await Promise.all(
splitReceivers.map(async (receiver) => {
Expand Down Expand Up @@ -492,11 +498,11 @@ const projectResolvers = {

const support = supportItems.filter((item) => item !== null);

const oneTimeDonationSupport =
await supportDataSource.getOneTimeDonationSupportByAccountIdOnChain(
projectId,
projectChain,
);
const oneTimeDonationSupport = await getProjectOneTimeDonationSupport(
projectId,
projectChain,
supportDataSource,
);

return [...support, ...oneTimeDonationSupport];
},
Expand Down Expand Up @@ -539,11 +545,11 @@ const projectResolvers = {
},
}: Context,
) => {
const splitsReceivers =
await supportDataSource.getSplitSupportByReceiverIdOnChain(
projectId,
projectChain,
);
const splitsReceivers = await getProjectSplitSupport(
projectId,
projectChain,
supportDataSource,
);

const supportItems = await Promise.all(
splitsReceivers.map(async (s) => {
Expand Down Expand Up @@ -632,12 +638,11 @@ const projectResolvers = {

const support = supportItems.filter((item) => item !== null);

// `GivenEventModelDataValues`s that represent one time donations to the Project.
const oneTimeDonationSupport =
await supportDataSource.getOneTimeDonationSupportByAccountIdOnChain(
projectId,
projectChain,
);
const oneTimeDonationSupport = await getProjectOneTimeDonationSupport(
projectId,
projectChain,
supportDataSource,
);

return [...support, ...oneTimeDonationSupport];
},
Expand Down
67 changes: 67 additions & 0 deletions src/project/projectSupportHelpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import type { AccountId, DbSchema } from '../common/types';
import { isRepoDriverId } from '../utils/assert';
import { calcSubRepoDriverId } from '../utils/repoSubAccountIdUtils';
import type SupportDataSource from '../dataLoaders/SupportDataSource';
import type { SplitsReceiverModelDataValues } from '../models/SplitsReceiverModel';
import type { GivenEventModelDataValues } from '../given-event/GivenEventModel';

/**
* Gets account IDs to query for project support (main account + sub-account if applicable)
*/
export async function getProjectAccountIdsToQuery(
projectId: AccountId,
projectChain: DbSchema,
): Promise<AccountId[]> {
const accountIds = [projectId];

if (isRepoDriverId(projectId)) {
const subAccountId = await calcSubRepoDriverId(projectId, projectChain);
accountIds.push(subAccountId);
}

return accountIds;
}

/**
* Queries split support for a project (including both main and sub-account)
*/
export async function getProjectSplitSupport(
projectId: AccountId,
projectChain: DbSchema,
supportDataSource: SupportDataSource,
): Promise<SplitsReceiverModelDataValues[]> {
const accountIdsForSplitSupport = await getProjectAccountIdsToQuery(
projectId,
projectChain,
);

const splitReceiversResults = await Promise.all(
accountIdsForSplitSupport.map((accountId) =>
supportDataSource.getSplitSupportByReceiverIdOnChain(
accountId,
projectChain,
),
),
);

return splitReceiversResults.flat();
}

/**
* Queries one-time donation support for a project (including both main and sub-account)
*/
export async function getProjectOneTimeDonationSupport(
projectId: AccountId,
projectChain: DbSchema,
supportDataSource: SupportDataSource,
): Promise<GivenEventModelDataValues[]> {
const accountIdsToQuery = await getProjectAccountIdsToQuery(
projectId,
projectChain,
);

return supportDataSource.getOneTimeDonationSupportByAccountIdsOnChain(
accountIdsToQuery,
projectChain,
);
}
14 changes: 11 additions & 3 deletions src/utils/repoSubAccountIdUtils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import dripsContracts from '../common/dripsContracts';
import type { DbSchema, RepoDriverId } from '../common/types';
import type {
DbSchema,
RepoDriverId,
RepoSubAccountDriverId,
} from '../common/types';
import { assertIsRepoDriverId, assertIsRepoSubAccountDriverId } from './assert';
import { dbSchemaToChain } from './chainSchemaMappings';
import shouldNeverHappen from './shouldNeverHappen';
Expand Down Expand Up @@ -50,6 +54,10 @@ export async function calcParentRepoDriverId(
export async function calcSubRepoDriverId(
parentId: string,
chain: DbSchema,
): Promise<RepoDriverId> {
return transformRepoDriverId(parentId, 'toSub', chain);
): Promise<RepoSubAccountDriverId> {
return transformRepoDriverId(
parentId,
'toSub',
chain,
) as unknown as Promise<RepoSubAccountDriverId>;
}