feat(dashboard): cache snapshot data and add credits activity analytics
This commit is contained in:
102
convex/dashboard.ts
Normal file
102
convex/dashboard.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import { query } from "./_generated/server";
|
||||
|
||||
import { optionalAuth } from "./helpers";
|
||||
import { prioritizeRecentCreditTransactions } from "../lib/credits-activity";
|
||||
import { MONTHLY_TIER_CREDITS, normalizeBillingTier } from "../lib/tier-credits";
|
||||
|
||||
const DEFAULT_TIER = "free" as const;
|
||||
const DEFAULT_SUBSCRIPTION_STATUS = "active" as const;
|
||||
|
||||
export const getSnapshot = query({
|
||||
args: {},
|
||||
handler: async (ctx) => {
|
||||
const user = await optionalAuth(ctx);
|
||||
|
||||
if (!user) {
|
||||
return {
|
||||
balance: { balance: 0, reserved: 0, available: 0, monthlyAllocation: 0 },
|
||||
subscription: {
|
||||
tier: DEFAULT_TIER,
|
||||
status: DEFAULT_SUBSCRIPTION_STATUS,
|
||||
currentPeriodEnd: undefined,
|
||||
},
|
||||
usageStats: {
|
||||
monthlyUsage: 0,
|
||||
totalGenerations: 0,
|
||||
monthlyCredits: MONTHLY_TIER_CREDITS[DEFAULT_TIER],
|
||||
},
|
||||
recentTransactions: [],
|
||||
canvases: [],
|
||||
generatedAt: Date.now(),
|
||||
};
|
||||
}
|
||||
|
||||
const [balanceRow, subscriptionRow, usageTransactions, recentTransactionsRaw, canvases] =
|
||||
await Promise.all([
|
||||
ctx.db
|
||||
.query("creditBalances")
|
||||
.withIndex("by_user", (q) => q.eq("userId", user.userId))
|
||||
.unique(),
|
||||
ctx.db
|
||||
.query("subscriptions")
|
||||
.withIndex("by_user", (q) => q.eq("userId", user.userId))
|
||||
.order("desc")
|
||||
.first(),
|
||||
ctx.db
|
||||
.query("creditTransactions")
|
||||
.withIndex("by_user_type", (q) => q.eq("userId", user.userId).eq("type", "usage"))
|
||||
.order("desc")
|
||||
.collect(),
|
||||
ctx.db
|
||||
.query("creditTransactions")
|
||||
.withIndex("by_user", (q) => q.eq("userId", user.userId))
|
||||
.order("desc")
|
||||
.take(80),
|
||||
ctx.db
|
||||
.query("canvases")
|
||||
.withIndex("by_owner_updated", (q) => q.eq("ownerId", user.userId))
|
||||
.order("desc")
|
||||
.collect(),
|
||||
]);
|
||||
|
||||
const tier = normalizeBillingTier(subscriptionRow?.tier);
|
||||
const monthStart = new Date(new Date().getFullYear(), new Date().getMonth(), 1).getTime();
|
||||
let monthlyUsage = 0;
|
||||
let totalGenerations = 0;
|
||||
|
||||
for (const transaction of usageTransactions) {
|
||||
if (transaction._creationTime < monthStart) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (transaction.status === "committed") {
|
||||
monthlyUsage += Math.abs(transaction.amount);
|
||||
totalGenerations += 1;
|
||||
}
|
||||
}
|
||||
|
||||
const balance = {
|
||||
balance: balanceRow?.balance ?? 0,
|
||||
reserved: balanceRow?.reserved ?? 0,
|
||||
available: (balanceRow?.balance ?? 0) - (balanceRow?.reserved ?? 0),
|
||||
monthlyAllocation: balanceRow?.monthlyAllocation ?? MONTHLY_TIER_CREDITS[tier],
|
||||
};
|
||||
|
||||
return {
|
||||
balance,
|
||||
subscription: {
|
||||
tier: subscriptionRow?.tier ?? DEFAULT_TIER,
|
||||
status: subscriptionRow?.status ?? DEFAULT_SUBSCRIPTION_STATUS,
|
||||
currentPeriodEnd: subscriptionRow?.currentPeriodEnd,
|
||||
},
|
||||
usageStats: {
|
||||
monthlyUsage,
|
||||
totalGenerations,
|
||||
monthlyCredits: MONTHLY_TIER_CREDITS[tier],
|
||||
},
|
||||
recentTransactions: prioritizeRecentCreditTransactions(recentTransactionsRaw, 20),
|
||||
canvases,
|
||||
generatedAt: Date.now(),
|
||||
};
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user