Harden milestone sharing and X publishing
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
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 { buildMilestoneCardSnapshot, buildMilestonePostForQr, milestoneThreshold, socialLocale } from '@/lib/social-milestones';
|
||||
import { buildMilestonePostForQr, milestoneThreshold, socialLocale } from '@/lib/social-milestones';
|
||||
import { ensureSocialMilestoneCard } from '@/lib/social-milestones-server';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
@@ -16,9 +19,15 @@ export async function GET(request: NextRequest) {
|
||||
if (!user || user.socialPromptOptOut) return NextResponse.json({ milestone: null });
|
||||
|
||||
const milestone = await db.socialMilestone.findFirst({
|
||||
where: { userId, status: { in: ['detected', 'shown'] } },
|
||||
where: {
|
||||
userId,
|
||||
OR: [
|
||||
{ status: { in: ['detected', 'shown'] } },
|
||||
{ status: 'approved', brandStatus: { in: ['approved', 'processing', 'failed'] } },
|
||||
],
|
||||
},
|
||||
orderBy: { detectedAt: 'asc' },
|
||||
include: { qr: { select: { id: true, title: true } } },
|
||||
include: { qr: { select: { id: true, title: true, createdAt: true } } },
|
||||
});
|
||||
if (!milestone) return NextResponse.json({ milestone: null });
|
||||
|
||||
@@ -28,20 +37,22 @@ export async function GET(request: NextRequest) {
|
||||
const threshold = milestoneThreshold(milestone.kind);
|
||||
if (!threshold) return NextResponse.json({ milestone: null });
|
||||
const locale = socialLocale(request.nextUrl.searchParams.get('locale'));
|
||||
const allScanCount = await db.qRScan.count({ where: { qrId: milestone.qr.id } });
|
||||
const storedCard = milestone.cardData as Record<string, unknown> | null;
|
||||
const card = storedCard
|
||||
? { ...storedCard, totalScans: typeof storedCard.totalScans === 'number' ? storedCard.totalScans : allScanCount }
|
||||
: buildMilestoneCardSnapshot({
|
||||
primaryUseCase: user.primaryUseCase,
|
||||
qrTitle: milestone.qr.title,
|
||||
totalScans: allScanCount,
|
||||
totalUniqueScans: threshold,
|
||||
milestoneThreshold: threshold,
|
||||
reachedAt: milestone.detectedAt,
|
||||
trend: null,
|
||||
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: {
|
||||
@@ -51,9 +62,10 @@ export async function GET(request: NextRequest) {
|
||||
brandPostUrl: milestone.brandPostUrl,
|
||||
brandPostError: milestone.brandPostError,
|
||||
language: locale,
|
||||
shareUrl,
|
||||
preview: buildMilestonePostForQr(
|
||||
user.primaryUseCase,
|
||||
((milestone.cardData as { totalUniqueScans?: number } | null)?.totalUniqueScans || threshold),
|
||||
card.totalUniqueScans || threshold,
|
||||
null,
|
||||
locale,
|
||||
milestone.qr.title,
|
||||
|
||||
Reference in New Issue
Block a user