Files
nroxy/packages/db/src/account-store.ts
2026-03-10 14:03:52 +03:00

147 lines
4.0 KiB
TypeScript

import { getApproximateQuotaBucket, type QuotaBucket } from "@nproxy/domain";
import type { PrismaClient, SubscriptionStatus } from "@prisma/client";
import { Prisma } from "@prisma/client";
import { prisma as defaultPrisma } from "./prisma-client.js";
export interface UserAccountOverview {
user: {
id: string;
email: string;
isAdmin: boolean;
createdAt: Date;
};
subscription: {
id: string;
status: SubscriptionStatus;
renewsManually: boolean;
activatedAt?: Date;
currentPeriodStart?: Date;
currentPeriodEnd?: Date;
canceledAt?: Date;
plan: {
id: string;
code: string;
displayName: string;
monthlyRequestLimit: number;
monthlyPriceUsd: number;
billingCurrency: string;
isActive: boolean;
};
} | null;
quota: {
approximateBucket: QuotaBucket;
usedSuccessfulRequests: number;
monthlyRequestLimit: number;
} | null;
}
export function createPrismaAccountStore(database: PrismaClient = defaultPrisma) {
return {
async getUserAccountOverview(userId: string): Promise<UserAccountOverview | null> {
const user = await database.user.findUnique({
where: {
id: userId,
},
});
if (!user) {
return null;
}
const subscription = await database.subscription.findFirst({
where: {
userId,
},
include: {
plan: true,
},
orderBy: [
{ currentPeriodEnd: "desc" },
{ createdAt: "desc" },
],
});
const quota = subscription
? await buildQuotaSnapshot(database, userId, {
monthlyRequestLimit: subscription.plan.monthlyRequestLimit,
cycleStart:
subscription.currentPeriodStart ??
subscription.activatedAt ??
subscription.createdAt,
})
: null;
return {
user: {
id: user.id,
email: user.email,
isAdmin: user.isAdmin,
createdAt: user.createdAt,
},
subscription: subscription
? {
id: subscription.id,
status: subscription.status,
renewsManually: subscription.renewsManually,
...(subscription.activatedAt ? { activatedAt: subscription.activatedAt } : {}),
...(subscription.currentPeriodStart
? { currentPeriodStart: subscription.currentPeriodStart }
: {}),
...(subscription.currentPeriodEnd
? { currentPeriodEnd: subscription.currentPeriodEnd }
: {}),
...(subscription.canceledAt ? { canceledAt: subscription.canceledAt } : {}),
plan: {
id: subscription.plan.id,
code: subscription.plan.code,
displayName: subscription.plan.displayName,
monthlyRequestLimit: subscription.plan.monthlyRequestLimit,
monthlyPriceUsd: decimalToNumber(subscription.plan.monthlyPriceUsd),
billingCurrency: subscription.plan.billingCurrency,
isActive: subscription.plan.isActive,
},
}
: null,
quota,
};
},
};
}
function decimalToNumber(value: Prisma.Decimal | { toNumber(): number }): number {
return value.toNumber();
}
async function buildQuotaSnapshot(
database: PrismaClient,
userId: string,
input: {
monthlyRequestLimit: number;
cycleStart: Date;
},
): Promise<UserAccountOverview["quota"]> {
const usageAggregation = await database.usageLedgerEntry.aggregate({
where: {
userId,
entryType: "generation_success",
createdAt: {
gte: input.cycleStart,
},
},
_sum: {
deltaRequests: true,
},
});
const usedSuccessfulRequests = usageAggregation._sum.deltaRequests ?? 0;
return {
approximateBucket: getApproximateQuotaBucket({
used: usedSuccessfulRequests,
limit: input.monthlyRequestLimit,
}),
usedSuccessfulRequests,
monthlyRequestLimit: input.monthlyRequestLimit,
};
}