77 lines
2.8 KiB
TypeScript
77 lines
2.8 KiB
TypeScript
import { randomBytes } from 'crypto';
|
|
import { NextRequest, NextResponse } from 'next/server';
|
|
import { db } from '@/lib/db';
|
|
import { getWwwOrigin } from '@/lib/hosts';
|
|
import { getSessionUserId } from '@/lib/session';
|
|
import { buildMilestonePostForQr, milestoneThreshold, socialLocale } from '@/lib/social-milestones';
|
|
import { ensureSocialMilestoneCard } from '@/lib/social-milestones-server';
|
|
|
|
export const dynamic = 'force-dynamic';
|
|
|
|
// Returns at most one item. A missing response is treated as no consent, never as approval.
|
|
export async function GET(request: NextRequest) {
|
|
const userId = getSessionUserId();
|
|
if (!userId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
|
|
const user = await db.user.findUnique({
|
|
where: { id: userId }, select: { socialPromptOptOut: true, xHandle: true, primaryUseCase: true },
|
|
});
|
|
if (!user || user.socialPromptOptOut) return NextResponse.json({ milestone: null });
|
|
|
|
const milestone = await db.socialMilestone.findFirst({
|
|
where: {
|
|
userId,
|
|
OR: [
|
|
{ status: { in: ['detected', 'shown'] } },
|
|
{ status: 'approved', brandStatus: { in: ['approved', 'processing', 'failed'] } },
|
|
],
|
|
},
|
|
orderBy: { detectedAt: 'asc' },
|
|
include: { qr: { select: { id: true, title: true, createdAt: true } } },
|
|
});
|
|
if (!milestone) return NextResponse.json({ milestone: null });
|
|
|
|
if (milestone.status === 'detected') {
|
|
await db.socialMilestone.update({ where: { id: milestone.id }, data: { status: 'shown', shownAt: new Date() } });
|
|
}
|
|
const threshold = milestoneThreshold(milestone.kind);
|
|
if (!threshold) return NextResponse.json({ milestone: null });
|
|
const locale = socialLocale(request.nextUrl.searchParams.get('locale'));
|
|
const shareToken = milestone.shareToken || randomBytes(9).toString('base64url');
|
|
if (!milestone.shareToken) {
|
|
await db.socialMilestone.update({ where: { id: milestone.id }, data: { shareToken } });
|
|
}
|
|
const shareUrl = `${getWwwOrigin()}/s/m/${shareToken}`;
|
|
const card = await ensureSocialMilestoneCard({
|
|
milestoneId: milestone.id,
|
|
cardData: milestone.cardData,
|
|
kind: milestone.kind,
|
|
detectedAt: milestone.detectedAt,
|
|
language: locale,
|
|
qr: milestone.qr,
|
|
primaryUseCase: user.primaryUseCase,
|
|
refresh: ['detected', 'shown'].includes(milestone.status),
|
|
snapshotAt: new Date(),
|
|
});
|
|
|
|
return NextResponse.json({
|
|
milestone: {
|
|
id: milestone.id, qrTitle: milestone.qr.title, threshold,
|
|
defaultXHandle: user.xHandle,
|
|
brandStatus: milestone.brandStatus,
|
|
brandPostUrl: milestone.brandPostUrl,
|
|
brandPostError: milestone.brandPostError,
|
|
language: locale,
|
|
shareUrl,
|
|
preview: buildMilestonePostForQr(
|
|
user.primaryUseCase,
|
|
card.totalUniqueScans || threshold,
|
|
null,
|
|
locale,
|
|
milestone.qr.title,
|
|
),
|
|
card,
|
|
},
|
|
});
|
|
}
|