mirror of
https://github.com/eliasstepanik/core.git
synced 2026-01-10 23:48:26 +00:00
* Feat: change space assignment from statement to episode * feat: add default spaces and improve integration, space tools discovery in MCP * feat: change spaces to episode based * Feat: take multiple spaceIds while ingesting * Feat: modify mcp tool descriptions, add spaceId in mcp url * feat: add copy * bump: new version 0.1.24 --------- Co-authored-by: Manoj <saimanoj58@gmail.com>
224 lines
5.7 KiB
TypeScript
224 lines
5.7 KiB
TypeScript
/**
|
|
* Billing Service
|
|
*
|
|
* Handles all credit management and billing operations.
|
|
* Works in both self-hosted (unlimited) and cloud (metered) modes.
|
|
*/
|
|
|
|
import { prisma } from "~/db.server";
|
|
import { getPlanConfig } from "~/config/billing.server";
|
|
import type { PlanType, Subscription } from "@prisma/client";
|
|
|
|
export type CreditOperation = "addEpisode" | "search" | "chatMessage";
|
|
|
|
/**
|
|
* Reset monthly credits for a workspace
|
|
*/
|
|
export async function resetMonthlyCredits(workspaceId: string): Promise<void> {
|
|
const workspace = await prisma.workspace.findUnique({
|
|
where: { id: workspaceId },
|
|
include: {
|
|
Subscription: true,
|
|
user: {
|
|
include: {
|
|
UserUsage: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!workspace?.Subscription || !workspace.user?.UserUsage) {
|
|
throw new Error("Workspace, subscription, or user usage not found");
|
|
}
|
|
|
|
const subscription = workspace.Subscription;
|
|
const userUsage = workspace.user.UserUsage;
|
|
const now = new Date();
|
|
const nextMonth = new Date(now);
|
|
nextMonth.setMonth(nextMonth.getMonth() + 1);
|
|
|
|
// Create billing history record
|
|
await prisma.billingHistory.create({
|
|
data: {
|
|
subscriptionId: subscription.id,
|
|
periodStart: subscription.currentPeriodStart,
|
|
periodEnd: subscription.currentPeriodEnd,
|
|
monthlyCreditsAllocated: subscription.monthlyCredits,
|
|
creditsUsed: userUsage.usedCredits,
|
|
overageCreditsUsed: userUsage.overageCredits,
|
|
subscriptionAmount: 0, // TODO: Get from Stripe
|
|
usageAmount: subscription.overageAmount,
|
|
totalAmount: subscription.overageAmount,
|
|
},
|
|
});
|
|
|
|
// Reset credits
|
|
await prisma.$transaction([
|
|
prisma.userUsage.update({
|
|
where: { id: userUsage.id },
|
|
data: {
|
|
availableCredits: subscription.monthlyCredits,
|
|
usedCredits: 0,
|
|
overageCredits: 0,
|
|
lastResetAt: now,
|
|
nextResetAt: nextMonth,
|
|
// Reset usage breakdown
|
|
episodeCreditsUsed: 0,
|
|
searchCreditsUsed: 0,
|
|
chatCreditsUsed: 0,
|
|
},
|
|
}),
|
|
prisma.subscription.update({
|
|
where: { id: subscription.id },
|
|
data: {
|
|
currentPeriodStart: now,
|
|
currentPeriodEnd: nextMonth,
|
|
overageCreditsUsed: 0,
|
|
overageAmount: 0,
|
|
},
|
|
}),
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Initialize subscription for a workspace
|
|
*/
|
|
export async function initializeSubscription(
|
|
workspaceId: string,
|
|
planType: PlanType = "FREE",
|
|
): Promise<Subscription> {
|
|
const planConfig = getPlanConfig(planType);
|
|
const now = new Date();
|
|
const nextMonth = new Date(now);
|
|
nextMonth.setMonth(nextMonth.getMonth() + 1);
|
|
|
|
return await prisma.subscription.create({
|
|
data: {
|
|
workspaceId,
|
|
planType,
|
|
monthlyCredits: planConfig.monthlyCredits,
|
|
currentPeriodStart: now,
|
|
currentPeriodEnd: nextMonth,
|
|
enableUsageBilling: planConfig.enableOverage,
|
|
usagePricePerCredit: planConfig.enableOverage
|
|
? planConfig.overagePrice
|
|
: null,
|
|
},
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Ensure workspace has billing records initialized
|
|
*/
|
|
export async function ensureBillingInitialized(workspaceId: string) {
|
|
const workspace = await prisma.workspace.findUnique({
|
|
where: { id: workspaceId },
|
|
include: {
|
|
Subscription: true,
|
|
user: {
|
|
include: {
|
|
UserUsage: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!workspace?.user) {
|
|
throw new Error("Workspace or user not found");
|
|
}
|
|
|
|
// Initialize subscription if missing
|
|
if (!workspace.Subscription) {
|
|
await initializeSubscription(workspaceId, "FREE");
|
|
}
|
|
|
|
// Initialize user usage if missing
|
|
if (!workspace.user.UserUsage) {
|
|
const subscription = await prisma.subscription.findUnique({
|
|
where: { workspaceId },
|
|
});
|
|
|
|
if (subscription) {
|
|
await prisma.userUsage.create({
|
|
data: {
|
|
userId: workspace.user.id,
|
|
availableCredits: subscription.monthlyCredits,
|
|
usedCredits: 0,
|
|
overageCredits: 0,
|
|
lastResetAt: new Date(),
|
|
nextResetAt: subscription.currentPeriodEnd,
|
|
episodeCreditsUsed: 0,
|
|
searchCreditsUsed: 0,
|
|
chatCreditsUsed: 0,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get workspace usage summary
|
|
*/
|
|
export async function getUsageSummary(workspaceId: string) {
|
|
if (!workspaceId) {
|
|
return null;
|
|
}
|
|
|
|
// Ensure billing records exist for existing accounts
|
|
await ensureBillingInitialized(workspaceId);
|
|
|
|
const workspace = await prisma.workspace.findUnique({
|
|
where: { id: workspaceId },
|
|
include: {
|
|
Subscription: true,
|
|
user: {
|
|
include: {
|
|
UserUsage: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!workspace?.Subscription || !workspace.user?.UserUsage) {
|
|
return null;
|
|
}
|
|
|
|
const subscription = workspace.Subscription;
|
|
const userUsage = workspace.user.UserUsage;
|
|
const planConfig = getPlanConfig(subscription.planType);
|
|
|
|
return {
|
|
plan: {
|
|
type: subscription.planType,
|
|
name: planConfig.name,
|
|
},
|
|
credits: {
|
|
available: userUsage.availableCredits,
|
|
used: userUsage.usedCredits,
|
|
monthly: subscription.monthlyCredits,
|
|
overage: userUsage.overageCredits,
|
|
percentageUsed: Math.round(
|
|
(userUsage.usedCredits / subscription.monthlyCredits) * 100,
|
|
),
|
|
},
|
|
usage: {
|
|
episodes: userUsage.episodeCreditsUsed,
|
|
searches: userUsage.searchCreditsUsed,
|
|
chat: userUsage.chatCreditsUsed,
|
|
},
|
|
billingCycle: {
|
|
start: subscription.currentPeriodStart,
|
|
end: subscription.currentPeriodEnd,
|
|
daysRemaining: Math.ceil(
|
|
(subscription.currentPeriodEnd.getTime() - Date.now()) /
|
|
(1000 * 60 * 60 * 24),
|
|
),
|
|
},
|
|
overage: {
|
|
enabled: subscription.enableUsageBilling,
|
|
pricePerCredit: subscription.usagePricePerCredit,
|
|
amount: subscription.overageAmount,
|
|
},
|
|
};
|
|
}
|