147 lines
4.0 KiB
TypeScript
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,
|
|
};
|
|
}
|