Add consented social milestone posting
This commit is contained in:
48
src/app/(main)/api/social-milestones/[id]/route.ts
Normal file
48
src/app/(main)/api/social-milestones/[id]/route.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { db } from '@/lib/db';
|
||||
import { csrfProtection } from '@/lib/csrf';
|
||||
import { getSessionUserId } from '@/lib/session';
|
||||
import { buildMilestoneCard, buildMilestonePost, milestoneThreshold, normalizeXHandle, socialLocale } from '@/lib/social-milestones';
|
||||
|
||||
type Action = 'approve_brand' | 'self_share' | 'decline' | 'opt_out' | 'revoke';
|
||||
|
||||
export async function PATCH(request: NextRequest, { params }: { params: { id: string } }) {
|
||||
const csrf = csrfProtection(request);
|
||||
if (!csrf.valid) return NextResponse.json({ error: csrf.error }, { status: 403 });
|
||||
const userId = getSessionUserId();
|
||||
if (!userId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
|
||||
const body = await request.json().catch(() => null) as { action?: Action; withName?: boolean; xHandle?: string; language?: string } | null;
|
||||
if (!body || !['approve_brand', 'self_share', 'decline', 'opt_out', 'revoke'].includes(body.action || '')) {
|
||||
return NextResponse.json({ error: 'Invalid action' }, { status: 400 });
|
||||
}
|
||||
const milestone = await db.socialMilestone.findFirst({
|
||||
where: { id: params.id, userId }, include: { user: { select: { primaryUseCase: true } } },
|
||||
});
|
||||
if (!milestone) return NextResponse.json({ error: 'Not found' }, { status: 404 });
|
||||
const threshold = milestoneThreshold(milestone.kind);
|
||||
if (!threshold) return NextResponse.json({ error: 'Invalid milestone' }, { status: 400 });
|
||||
|
||||
if (body.action === 'revoke') {
|
||||
if (milestone.status !== 'approved') return NextResponse.json({ error: 'Only queued approvals can be revoked' }, { status: 409 });
|
||||
await db.socialMilestone.update({ where: { id: milestone.id }, data: { status: 'revoked', respondedAt: new Date() } });
|
||||
return NextResponse.json({ ok: true });
|
||||
}
|
||||
if (!['detected', 'shown'].includes(milestone.status)) return NextResponse.json({ error: 'This milestone has already been answered' }, { status: 409 });
|
||||
|
||||
const withName = body.action === 'approve_brand' && body.withName === true;
|
||||
const language = socialLocale(body.language);
|
||||
const xHandle = withName ? normalizeXHandle(body.xHandle || '') : null;
|
||||
if (withName && !xHandle) return NextResponse.json({ error: 'Enter a valid X handle' }, { status: 400 });
|
||||
const status = body.action === 'approve_brand' ? 'approved' : body.action === 'self_share' ? 'self_shared' : 'declined';
|
||||
const consentText = body.action === 'approve_brand'
|
||||
? buildMilestonePost(milestone.user.primaryUseCase, threshold, xHandle, language)
|
||||
: null;
|
||||
const now = new Date();
|
||||
await db.$transaction([
|
||||
db.socialMilestone.update({ where: { id: milestone.id }, data: { status, withName, consentText, language, cardData: body.action === 'approve_brand' ? buildMilestoneCard(milestone.user.primaryUseCase, threshold, language) : undefined, respondedAt: now } }),
|
||||
...(body.action === 'opt_out' ? [db.user.update({ where: { id: userId }, data: { socialPromptOptOut: true } })] : []),
|
||||
...(withName ? [db.user.update({ where: { id: userId }, data: { xHandle } })] : []),
|
||||
]);
|
||||
return NextResponse.json({ ok: true, consentText });
|
||||
}
|
||||
Reference in New Issue
Block a user