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
125 changes: 94 additions & 31 deletions packages/app/control/src/services/admin/app/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,31 @@ const COLUMN_MAPPINGS: Record<string, string> = {
email: 'u.email',
createdAt: 'u."createdAt"',
updatedAt: 'u."updatedAt"',
// Membership columns (primary keys)
membershipRole: 'am.role',
membershipStatus: 'am.status',
membershipTotalSpent: 'am."totalSpent"',
membershipCreatedAt: 'am."createdAt"',
totalTransactions: 'COUNT(t.id)',
totalInputTokens: 'COALESCE(SUM(tm."inputTokens"), 0)',
totalOutputTokens: 'COALESCE(SUM(tm."outputTokens"), 0)',
totalTokens: 'COALESCE(SUM(tm."totalTokens"), 0)',
totalSpent: 'COALESCE(SUM(t."totalCost"), 0)',
totalAppProfit: 'COALESCE(SUM(t."appProfit"), 0)',
totalMarkupProfit: 'COALESCE(SUM(t."markUpProfit"), 0)',
totalReferralProfit: 'COALESCE(SUM(t."referralProfit"), 0)',
lastTransactionAt: 'MAX(t."createdAt")',
// Membership columns (dot-notation aliases from frontend)
'membership.role': 'am.role',
'membership.status': 'am.status',
'membership.totalSpent': 'am."totalSpent"',
'membership.createdAt': 'am."createdAt"',
// Membership columns (snake_case aliases)
membership_role: 'am.role',
membership_status: 'am.status',
membership_total_spent: 'am."totalSpent"',
membership_created_at: 'am."createdAt"',
// Transaction aggregates
totalTransactions: 'COALESCE(tx."totalTransactions", 0)',
totalInputTokens: 'COALESCE(tx."totalInputTokens", 0)',
totalOutputTokens: 'COALESCE(tx."totalOutputTokens", 0)',
totalTokens: 'COALESCE(tx."totalTokens", 0)',
totalSpent: 'COALESCE(tx."totalSpent", 0)',
totalAppProfit: 'COALESCE(tx."totalAppProfit", 0)',
totalMarkupProfit: 'COALESCE(tx."totalMarkupProfit", 0)',
totalReferralProfit: 'COALESCE(tx."totalReferralProfit", 0)',
lastTransactionAt: 'tx."lastTransactionAt"',
};

// Use shared builder with optional appId parameter as initial parameter
Expand Down Expand Up @@ -71,11 +83,15 @@ export const getAppUsersWithPagination = async (
'totalMarkupProfit',
'totalReferralProfit',
'membershipTotalSpent',
'membership.totalSpent',
'membership_total_spent',
],
dateColumns: [
'createdAt',
'updatedAt',
'membershipCreatedAt',
'membership.createdAt',
'membership_created_at',
'lastTransactionAt',
],
uuidColumns: ['id'],
Expand All @@ -101,22 +117,39 @@ export const getAppUsersWithPagination = async (
am.status as "membershipStatus",
am."totalSpent" as "membershipTotalSpent",
am."createdAt" as "membershipCreatedAt",
COUNT(t.id) as "totalTransactions",
COALESCE(SUM(tm."inputTokens"), 0) as "totalInputTokens",
COALESCE(SUM(tm."outputTokens"), 0) as "totalOutputTokens",
COALESCE(SUM(tm."totalTokens"), 0) as "totalTokens",
COALESCE(SUM(t."totalCost"), 0) as "totalSpent",
COALESCE(SUM(t."appProfit"), 0) as "totalAppProfit",
COALESCE(SUM(t."markUpProfit"), 0) as "totalMarkupProfit",
COALESCE(SUM(t."referralProfit"), 0) as "totalReferralProfit",
MAX(t."createdAt") as "lastTransactionAt"
COALESCE(tx."totalTransactions", 0) as "totalTransactions",
COALESCE(tx."totalInputTokens", 0) as "totalInputTokens",
COALESCE(tx."totalOutputTokens", 0) as "totalOutputTokens",
COALESCE(tx."totalTokens", 0) as "totalTokens",
COALESCE(tx."totalSpent", 0) as "totalSpent",
COALESCE(tx."totalAppProfit", 0) as "totalAppProfit",
COALESCE(tx."totalMarkupProfit", 0) as "totalMarkupProfit",
COALESCE(tx."totalReferralProfit", 0) as "totalReferralProfit",
tx."lastTransactionAt" as "lastTransactionAt"
FROM "users" u
INNER JOIN "app_memberships" am ON u.id = am."userId"
LEFT JOIN "transactions" t ON u.id = t."userId" AND am."echoAppId" = t."echoAppId"
LEFT JOIN "transaction_metadata" tm ON t."transactionMetadataId" = tm.id
LEFT JOIN LATERAL (
SELECT
COUNT(*) as "totalTransactions",
COALESCE(SUM(tm."inputTokens"), 0) as "totalInputTokens",
COALESCE(SUM(tm."outputTokens"), 0) as "totalOutputTokens",
COALESCE(SUM(tm."totalTokens"), 0) as "totalTokens",
COALESCE(SUM(t."totalCost"), 0) as "totalSpent",
COALESCE(SUM(t."appProfit"), 0) as "totalAppProfit",
COALESCE(SUM(t."markUpProfit"), 0) as "totalMarkupProfit",
COALESCE(SUM(t."referralProfit"), 0) as "totalReferralProfit",
MAX(t."createdAt") as "lastTransactionAt"
FROM "transactions" t
LEFT JOIN "transaction_metadata" tm ON t."transactionMetadataId" = tm.id
WHERE t."userId" = u.id AND t."echoAppId" = am."echoAppId"
) tx ON TRUE
${whereClause}
GROUP BY u.id, u.name, u.email, u."createdAt", u."updatedAt", u."totalPaid", u."totalSpent",
am.role, am.status, am."totalSpent", am."createdAt"
GROUP BY
u.id, u.name, u.email, u."createdAt", u."updatedAt", u."totalPaid", u."totalSpent",
am.role, am.status, am."totalSpent", am."createdAt",
tx."totalTransactions", tx."totalInputTokens", tx."totalOutputTokens", tx."totalTokens",
tx."totalSpent", tx."totalAppProfit", tx."totalMarkupProfit", tx."totalReferralProfit",
tx."lastTransactionAt"
${havingClause}
${orderByClause}
LIMIT $${parameters.length + 1}
Expand Down Expand Up @@ -158,20 +191,50 @@ export const getAppUsersWithPagination = async (
}>;

// Build count query with same filters
const countQuery = `
SELECT COUNT(DISTINCT u.id) as count
const countSourceQuery = `
SELECT
u.id,
am.role as "membershipRole",
am.status as "membershipStatus",
am."totalSpent" as "membershipTotalSpent",
am."createdAt" as "membershipCreatedAt",
COALESCE(tx."totalTransactions", 0) as "totalTransactions",
COALESCE(tx."totalInputTokens", 0) as "totalInputTokens",
COALESCE(tx."totalOutputTokens", 0) as "totalOutputTokens",
COALESCE(tx."totalTokens", 0) as "totalTokens",
COALESCE(tx."totalSpent", 0) as "totalSpent",
COALESCE(tx."totalAppProfit", 0) as "totalAppProfit",
COALESCE(tx."totalMarkupProfit", 0) as "totalMarkupProfit",
COALESCE(tx."totalReferralProfit", 0) as "totalReferralProfit",
tx."lastTransactionAt" as "lastTransactionAt"
FROM "users" u
INNER JOIN "app_memberships" am ON u.id = am."userId"
LEFT JOIN "transactions" t ON u.id = t."userId" AND am."echoAppId" = t."echoAppId"
LEFT JOIN "transaction_metadata" tm ON t."transactionMetadataId" = tm.id
LEFT JOIN LATERAL (
SELECT
COUNT(*) as "totalTransactions",
COALESCE(SUM(tm."inputTokens"), 0) as "totalInputTokens",
COALESCE(SUM(tm."outputTokens"), 0) as "totalOutputTokens",
COALESCE(SUM(tm."totalTokens"), 0) as "totalTokens",
COALESCE(SUM(t."totalCost"), 0) as "totalSpent",
COALESCE(SUM(t."appProfit"), 0) as "totalAppProfit",
COALESCE(SUM(t."markUpProfit"), 0) as "totalMarkupProfit",
COALESCE(SUM(t."referralProfit"), 0) as "totalReferralProfit",
MAX(t."createdAt") as "lastTransactionAt"
FROM "transactions" t
LEFT JOIN "transaction_metadata" tm ON t."transactionMetadataId" = tm.id
WHERE t."userId" = u.id AND t."echoAppId" = am."echoAppId"
) tx ON TRUE
${whereClause}
${havingClause ? `GROUP BY u.id ${havingClause}` : ''}
GROUP BY
u.id,
am.role, am.status, am."totalSpent", am."createdAt",
tx."totalTransactions", tx."totalInputTokens", tx."totalOutputTokens", tx."totalTokens",
tx."totalSpent", tx."totalAppProfit", tx."totalMarkupProfit", tx."totalReferralProfit",
tx."lastTransactionAt"
${havingClause}
`;

// If we have HAVING clauses, we need to count the grouped results
const totalCountQuery = havingClause
? `SELECT COUNT(*) as count FROM (${countQuery}) as filtered_results`
: countQuery;
const totalCountQuery = `SELECT COUNT(*) as count FROM (${countSourceQuery}) as filtered_results`;

const totalCount = (await db.$queryRawUnsafe(
totalCountQuery,
Expand Down
132 changes: 87 additions & 45 deletions packages/app/control/src/services/admin/user/apps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,18 @@ const COLUMN_MAPPINGS: Record<string, string> = {
description: 'a.description',
createdAt: 'a."createdAt"',
updatedAt: 'a."updatedAt"',
totalUsers: 'COUNT(DISTINCT am."userId")',
totalTransactions: 'COUNT(t.id)',
totalInputTokens: 'COALESCE(SUM(tm."inputTokens"), 0)',
totalOutputTokens: 'COALESCE(SUM(tm."outputTokens"), 0)',
totalTokens: 'COALESCE(SUM(tm."totalTokens"), 0)',
totalSpent: 'COALESCE(SUM(t."totalCost"), 0)',
totalSpentFreeTier:
'COALESCE(SUM(CASE WHEN t."spendPoolId" IS NOT NULL THEN t."totalCost" ELSE 0 END), 0)',
totalSpentUserBalances:
'COALESCE(SUM(CASE WHEN t."spendPoolId" IS NULL THEN t."totalCost" ELSE 0 END), 0)',
totalReferralProfitEarned: 'COALESCE(SUM(t."referralProfit"), 0)',
totalMarkupProfitEarned: 'COALESCE(SUM(t."markUpProfit"), 0)',
totalTransactionCosts: 'COALESCE(SUM(t."rawTransactionCost"), 0)',
lastTransactionAt: 'MAX(t."createdAt")',
totalUsers: 'COALESCE(u_stat."totalUsers", 0)',
totalTransactions: 'COALESCE(tx."totalTransactions", 0)',
totalInputTokens: 'COALESCE(tx."totalInputTokens", 0)',
totalOutputTokens: 'COALESCE(tx."totalOutputTokens", 0)',
totalTokens: 'COALESCE(tx."totalTokens", 0)',
totalSpent: 'COALESCE(tx."totalSpent", 0)',
totalSpentFreeTier: 'COALESCE(tx."totalSpentFreeTier", 0)',
totalSpentUserBalances: 'COALESCE(tx."totalSpentUserBalances", 0)',
totalReferralProfitEarned: 'COALESCE(tx."totalReferralProfitEarned", 0)',
totalMarkupProfitEarned: 'COALESCE(tx."totalMarkupProfitEarned", 0)',
totalTransactionCosts: 'COALESCE(tx."totalTransactionCosts", 0)',
lastTransactionAt: 'tx."lastTransactionAt"',
};

// Helper function to get payout information
Expand Down Expand Up @@ -100,9 +98,7 @@ export const getUserAppsWithPagination = async (
{
columnMappings: COLUMN_MAPPINGS,
defaultWhere:
'WHERE owner_am."userId" = $1::uuid AND owner_am.role = \'' +
'owner' +
"'",
"WHERE EXISTS (SELECT 1 FROM \"app_memberships\" owner_am WHERE owner_am.\"echoAppId\" = a.id AND owner_am.\"userId\" = $1::uuid AND owner_am.role = 'owner')",
aggregatedColumns: [
'totalUsers',
'totalTransactions',
Expand Down Expand Up @@ -148,25 +144,49 @@ export const getUserAppsWithPagination = async (
a.description,
a."createdAt",
a."updatedAt",
COUNT(DISTINCT am."userId") as "totalUsers",
COUNT(t.id) as "totalTransactions",
COALESCE(SUM(tm."inputTokens"), 0) as "totalInputTokens",
COALESCE(SUM(tm."outputTokens"), 0) as "totalOutputTokens",
COALESCE(SUM(tm."totalTokens"), 0) as "totalTokens",
COALESCE(SUM(t."totalCost"), 0) as "totalSpent",
COALESCE(SUM(CASE WHEN t."spendPoolId" IS NOT NULL THEN t."totalCost" ELSE 0 END), 0) as "totalSpentFreeTier",
COALESCE(SUM(CASE WHEN t."spendPoolId" IS NULL THEN t."totalCost" ELSE 0 END), 0) as "totalSpentUserBalances",
COALESCE(SUM(t."referralProfit"), 0) as "totalReferralProfitEarned",
COALESCE(SUM(t."markUpProfit"), 0) as "totalMarkupProfitEarned",
COALESCE(SUM(t."rawTransactionCost"), 0) as "totalTransactionCosts",
MAX(t."createdAt") as "lastTransactionAt"
COALESCE(u_stat."totalUsers", 0) as "totalUsers",
COALESCE(tx."totalTransactions", 0) as "totalTransactions",
COALESCE(tx."totalInputTokens", 0) as "totalInputTokens",
COALESCE(tx."totalOutputTokens", 0) as "totalOutputTokens",
COALESCE(tx."totalTokens", 0) as "totalTokens",
COALESCE(tx."totalSpent", 0) as "totalSpent",
COALESCE(tx."totalSpentFreeTier", 0) as "totalSpentFreeTier",
COALESCE(tx."totalSpentUserBalances", 0) as "totalSpentUserBalances",
COALESCE(tx."totalReferralProfitEarned", 0) as "totalReferralProfitEarned",
COALESCE(tx."totalMarkupProfitEarned", 0) as "totalMarkupProfitEarned",
COALESCE(tx."totalTransactionCosts", 0) as "totalTransactionCosts",
tx."lastTransactionAt" as "lastTransactionAt"
FROM "echo_apps" a
INNER JOIN "app_memberships" owner_am ON a.id = owner_am."echoAppId"
LEFT JOIN "app_memberships" am ON a.id = am."echoAppId"
LEFT JOIN "transactions" t ON a.id = t."echoAppId"
LEFT JOIN "transaction_metadata" tm ON t."transactionMetadataId" = tm.id
LEFT JOIN LATERAL (
SELECT
COUNT(*) as "totalTransactions",
COALESCE(SUM(tm."inputTokens"), 0) as "totalInputTokens",
COALESCE(SUM(tm."outputTokens"), 0) as "totalOutputTokens",
COALESCE(SUM(tm."totalTokens"), 0) as "totalTokens",
COALESCE(SUM(t."totalCost"), 0) as "totalSpent",
COALESCE(SUM(CASE WHEN t."spendPoolId" IS NOT NULL THEN t."totalCost" ELSE 0 END), 0) as "totalSpentFreeTier",
COALESCE(SUM(CASE WHEN t."spendPoolId" IS NULL THEN t."totalCost" ELSE 0 END), 0) as "totalSpentUserBalances",
COALESCE(SUM(t."referralProfit"), 0) as "totalReferralProfitEarned",
COALESCE(SUM(t."markUpProfit"), 0) as "totalMarkupProfitEarned",
COALESCE(SUM(t."rawTransactionCost"), 0) as "totalTransactionCosts",
MAX(t."createdAt") as "lastTransactionAt"
FROM "transactions" t
LEFT JOIN "transaction_metadata" tm ON t."transactionMetadataId" = tm.id
WHERE t."echoAppId" = a.id
) tx ON TRUE
LEFT JOIN LATERAL (
SELECT COUNT(DISTINCT am."userId") as "totalUsers"
FROM "app_memberships" am
WHERE am."echoAppId" = a.id
) u_stat ON TRUE
${whereClause}
GROUP BY a.id, a.name, a.description, a."createdAt", a."updatedAt"
GROUP BY
a.id, a.name, a.description, a."createdAt", a."updatedAt",
u_stat."totalUsers",
tx."totalTransactions", tx."totalInputTokens", tx."totalOutputTokens", tx."totalTokens",
tx."totalSpent", tx."totalSpentFreeTier", tx."totalSpentUserBalances",
tx."totalReferralProfitEarned", tx."totalMarkupProfitEarned", tx."totalTransactionCosts",
tx."lastTransactionAt"
${havingClause}
${orderByClause}
LIMIT $${parameters.length + 1}
Expand Down Expand Up @@ -209,21 +229,43 @@ export const getUserAppsWithPagination = async (
const payoutInfo = await getPayoutInformation(appIds);

// Build count query with same filters
const countQuery = `
SELECT COUNT(DISTINCT a.id) as count
const countSourceQuery = `
SELECT a.id
FROM "echo_apps" a
INNER JOIN "app_memberships" owner_am ON a.id = owner_am."echoAppId"
LEFT JOIN "app_memberships" am ON a.id = am."echoAppId"
LEFT JOIN "transactions" t ON a.id = t."echoAppId"
LEFT JOIN "transaction_metadata" tm ON t."transactionMetadataId" = tm.id
LEFT JOIN LATERAL (
SELECT
COUNT(*) as "totalTransactions",
COALESCE(SUM(tm."inputTokens"), 0) as "totalInputTokens",
COALESCE(SUM(tm."outputTokens"), 0) as "totalOutputTokens",
COALESCE(SUM(tm."totalTokens"), 0) as "totalTokens",
COALESCE(SUM(t."totalCost"), 0) as "totalSpent",
COALESCE(SUM(CASE WHEN t."spendPoolId" IS NOT NULL THEN t."totalCost" ELSE 0 END), 0) as "totalSpentFreeTier",
COALESCE(SUM(CASE WHEN t."spendPoolId" IS NULL THEN t."totalCost" ELSE 0 END), 0) as "totalSpentUserBalances",
COALESCE(SUM(t."referralProfit"), 0) as "totalReferralProfitEarned",
COALESCE(SUM(t."markUpProfit"), 0) as "totalMarkupProfitEarned",
COALESCE(SUM(t."rawTransactionCost"), 0) as "totalTransactionCosts",
MAX(t."createdAt") as "lastTransactionAt"
FROM "transactions" t
LEFT JOIN "transaction_metadata" tm ON t."transactionMetadataId" = tm.id
WHERE t."echoAppId" = a.id
) tx ON TRUE
LEFT JOIN LATERAL (
SELECT COUNT(DISTINCT am."userId") as "totalUsers"
FROM "app_memberships" am
WHERE am."echoAppId" = a.id
) u_stat ON TRUE
${whereClause}
${havingClause ? `GROUP BY a.id ${havingClause}` : ''}
GROUP BY
a.id,
u_stat."totalUsers",
tx."totalTransactions", tx."totalInputTokens", tx."totalOutputTokens", tx."totalTokens",
tx."totalSpent", tx."totalSpentFreeTier", tx."totalSpentUserBalances",
tx."totalReferralProfitEarned", tx."totalMarkupProfitEarned", tx."totalTransactionCosts",
tx."lastTransactionAt"
${havingClause}
`;

// If we have HAVING clauses, we need to count the grouped results
const totalCountQuery = havingClause
? `SELECT COUNT(*) as count FROM (${countQuery}) as filtered_results`
: countQuery;
const totalCountQuery = `SELECT COUNT(*) as count FROM (${countSourceQuery}) as filtered_results`;

const totalCount = (await db.$queryRawUnsafe(
totalCountQuery,
Expand Down
Loading
Loading