TikTok V5 + Security

This commit is contained in:
2026-07-11 22:09:10 +02:00
parent 671c1a1559
commit d542f849aa
36 changed files with 740 additions and 463 deletions

View File

@@ -1,13 +1,13 @@
import { NextRequest, NextResponse } from 'next/server';
import { cookies } from 'next/headers';
import { stripe } from '@/lib/stripe';
import { db } from '@/lib/db';
import { rateLimit, getClientIdentifier, RateLimits } from '@/lib/rateLimit';
import { scoreUserLifecycle } from '@/lib/revops-server';
import { getSessionUserId } from '@/lib/session';
import { stripe } from '@/lib/stripe';
import { db } from '@/lib/db';
import { rateLimit, getClientIdentifier, RateLimits } from '@/lib/rateLimit';
import { scoreUserLifecycle } from '@/lib/revops-server';
export async function POST(request: NextRequest) {
try {
const userId = cookies().get('userId')?.value;
const userId = getSessionUserId();
// Rate Limiting (user-based)
const clientId = userId || getClientIdentifier(request);
@@ -34,14 +34,14 @@ export async function POST(request: NextRequest) {
}
// Get user with subscription info
const user = await db.user.findUnique({
where: { id: userId },
select: {
stripeSubscriptionId: true,
stripeCurrentPeriodEnd: true,
plan: true,
},
});
const user = await db.user.findUnique({
where: { id: userId },
select: {
stripeSubscriptionId: true,
stripeCurrentPeriodEnd: true,
plan: true,
},
});
if (!user) {
return NextResponse.json({ error: 'User not found' }, { status: 404 });
@@ -55,42 +55,42 @@ export async function POST(request: NextRequest) {
// No active subscription
if (!user.stripeSubscriptionId) {
// Just update plan to FREE if somehow plan is not FREE but no subscription
await db.user.update({
where: { id: userId },
data: {
await db.user.update({
where: { id: userId },
data: {
plan: 'FREE',
stripePriceId: null,
stripeCurrentPeriodEnd: null,
},
});
await scoreUserLifecycle(userId, 'subscription_deleted');
return NextResponse.json({ success: true });
},
});
await scoreUserLifecycle(userId, 'subscription_deleted');
return NextResponse.json({ success: true });
}
// Schedule cancellation at the end of the paid period so paid features stay active.
const subscription: any = await stripe.subscriptions.update(user.stripeSubscriptionId, {
cancel_at_period_end: true,
});
const periodEndTimestamp = subscription.current_period_end
|| subscription.currentPeriodEnd
|| subscription.billing_cycle_anchor;
const currentPeriodEnd = periodEndTimestamp
? new Date(periodEndTimestamp * 1000)
: user.stripeCurrentPeriodEnd;
// Keep the paid plan locally until Stripe sends customer.subscription.deleted.
await db.user.update({
where: { id: userId },
data: {
stripeCurrentPeriodEnd: currentPeriodEnd,
},
});
await scoreUserLifecycle(userId, 'subscription_canceled_at_period_end');
return NextResponse.json({ success: true, currentPeriodEnd });
// Schedule cancellation at the end of the paid period so paid features stay active.
const subscription: any = await stripe.subscriptions.update(user.stripeSubscriptionId, {
cancel_at_period_end: true,
});
const periodEndTimestamp = subscription.current_period_end
|| subscription.currentPeriodEnd
|| subscription.billing_cycle_anchor;
const currentPeriodEnd = periodEndTimestamp
? new Date(periodEndTimestamp * 1000)
: user.stripeCurrentPeriodEnd;
// Keep the paid plan locally until Stripe sends customer.subscription.deleted.
await db.user.update({
where: { id: userId },
data: {
stripeCurrentPeriodEnd: currentPeriodEnd,
},
});
await scoreUserLifecycle(userId, 'subscription_canceled_at_period_end');
return NextResponse.json({ success: true, currentPeriodEnd });
} catch (error) {
console.error('Error canceling subscription:', error);
return NextResponse.json(