mirror of
https://github.com/yusufipk/OpenFrame.git
synced 2026-09-11 17:46:06 +00:00
feat(billing): integrate Stripe for subscription management and billing access
- Added billing-related fields to the User model in the database. - Implemented functions for managing billing access, including trial periods and subscription statuses. - Created new billing utility functions for Stripe integration. - Updated onboarding page to include billing overview and workspace creation eligibility. - Enhanced route access checks to require billing access for certain actions. - Implemented cleanup scripts for expired billing workspaces and associated media. - Updated header component to conditionally show app navigation based on billing access. - Added new migrations for billing-related database changes.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { db, disconnectDb } from '../lib/db';
|
||||
import { cleanupExpiredBillingWorkspaces } from './expired-billing-cleanup';
|
||||
|
||||
const BUNNY_API_BASE = 'https://video.bunnycdn.com';
|
||||
const BUNNY_VIDEO_ID_PATTERN = /^[A-Za-z0-9_-]{8,128}$/;
|
||||
@@ -197,6 +198,10 @@ async function main() {
|
||||
console.log(`[bunny-orphan-cleanup] Starting (${dryRun ? 'dry-run' : 'delete mode'})`);
|
||||
console.log(`[bunny-orphan-cleanup] Grace period: ${graceHours}h`);
|
||||
|
||||
const expiredBillingCleanup = await cleanupExpiredBillingWorkspaces({ dryRun });
|
||||
console.log(`[bunny-orphan-cleanup] Expired owner workspaces scanned: ${expiredBillingCleanup.scanned}`);
|
||||
console.log(`[bunny-orphan-cleanup] Expired owner workspaces deleted: ${expiredBillingCleanup.deleted}`);
|
||||
|
||||
const { videos, scanned, skippedInvalid } = await listBunnyVideos(config);
|
||||
const eligible = videos.filter((video) => video.uploadedAt.getTime() <= cutoff);
|
||||
console.log(`[bunny-orphan-cleanup] Scanned: ${scanned}`);
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
import { db } from '../lib/db';
|
||||
import { buildExpiredBillingWhereInput } from '../lib/billing';
|
||||
import { collectWorkspaceMediaUrls, deleteMediaFilesBestEffort } from '../lib/r2-cleanup';
|
||||
import { cleanupBunnyStreamVideosBestEffort } from '../lib/bunny-stream-cleanup';
|
||||
|
||||
type ExpiredWorkspaceTarget = {
|
||||
id: string;
|
||||
ownerId: string;
|
||||
ownerEmail: string | null;
|
||||
};
|
||||
|
||||
async function getExpiredWorkspaceTargets(): Promise<ExpiredWorkspaceTarget[]> {
|
||||
const expiredOwners = await db.user.findMany({
|
||||
where: buildExpiredBillingWhereInput(),
|
||||
select: { id: true },
|
||||
});
|
||||
|
||||
if (expiredOwners.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return db.workspace.findMany({
|
||||
where: {
|
||||
ownerId: { in: expiredOwners.map((owner) => owner.id) },
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
ownerId: true,
|
||||
owner: {
|
||||
select: {
|
||||
email: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}).then((workspaces) =>
|
||||
workspaces.map((workspace) => ({
|
||||
id: workspace.id,
|
||||
ownerId: workspace.ownerId,
|
||||
ownerEmail: workspace.owner.email,
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
export async function cleanupExpiredBillingWorkspaces(options?: { dryRun?: boolean }) {
|
||||
const dryRun = options?.dryRun ?? false;
|
||||
const workspaces = await getExpiredWorkspaceTargets();
|
||||
|
||||
if (workspaces.length === 0) {
|
||||
return { scanned: 0, deleted: 0 };
|
||||
}
|
||||
|
||||
let deleted = 0;
|
||||
|
||||
for (const workspace of workspaces) {
|
||||
const [workspaceVersionRefs, workspaceAssetRefs, mediaUrls] = await Promise.all([
|
||||
db.videoVersion.findMany({
|
||||
where: {
|
||||
video: {
|
||||
project: {
|
||||
workspaceId: workspace.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
select: {
|
||||
providerId: true,
|
||||
videoId: true,
|
||||
},
|
||||
}),
|
||||
db.videoAsset.findMany({
|
||||
where: {
|
||||
provider: 'BUNNY',
|
||||
providerVideoId: { not: null },
|
||||
video: {
|
||||
project: {
|
||||
workspaceId: workspace.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
select: {
|
||||
providerVideoId: true,
|
||||
},
|
||||
}),
|
||||
collectWorkspaceMediaUrls(workspace.id),
|
||||
]);
|
||||
|
||||
if (dryRun) {
|
||||
const ownerLabel = workspace.ownerEmail ?? workspace.ownerId;
|
||||
console.log(
|
||||
`[expired-billing-cleanup] Would delete workspace ${workspace.id} owned by ${ownerLabel}`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const bunnyRefs = [
|
||||
...workspaceVersionRefs,
|
||||
...workspaceAssetRefs.map((asset) => ({
|
||||
providerId: 'bunny',
|
||||
videoId: asset.providerVideoId as string,
|
||||
})),
|
||||
];
|
||||
|
||||
await db.workspace.delete({ where: { id: workspace.id } });
|
||||
await Promise.all([
|
||||
cleanupBunnyStreamVideosBestEffort(bunnyRefs),
|
||||
deleteMediaFilesBestEffort(mediaUrls),
|
||||
]);
|
||||
deleted += 1;
|
||||
}
|
||||
|
||||
return { scanned: workspaces.length, deleted };
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { DeleteObjectCommand, ListObjectsV2Command, type ListObjectsV2CommandInput } from '@aws-sdk/client-s3';
|
||||
import { db, disconnectDb } from '../lib/db';
|
||||
import { r2Client, R2_BUCKET_NAME } from '../lib/r2';
|
||||
import { cleanupExpiredBillingWorkspaces } from './expired-billing-cleanup';
|
||||
|
||||
const UNATTACHED_UPLOAD_TTL_MS = 15 * 60 * 1000;
|
||||
const CHUNK_SIZE = 500;
|
||||
@@ -127,6 +128,10 @@ async function main() {
|
||||
const dryRun = process.argv.includes('--dry-run');
|
||||
console.log(`[r2-orphan-cleanup] Starting (${dryRun ? 'dry-run' : 'delete mode'})`);
|
||||
|
||||
const expiredBillingCleanup = await cleanupExpiredBillingWorkspaces({ dryRun });
|
||||
console.log(`[r2-orphan-cleanup] Expired owner workspaces scanned: ${expiredBillingCleanup.scanned}`);
|
||||
console.log(`[r2-orphan-cleanup] Expired owner workspaces deleted: ${expiredBillingCleanup.deleted}`);
|
||||
|
||||
const { candidates, scanned } = await listCleanupCandidates();
|
||||
console.log(`[r2-orphan-cleanup] Scanned: ${scanned}, eligible (old enough): ${candidates.length}`);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user