Files
QR-master/src/app/(main)/api/social-milestones/route.ts

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,
},
});
}