fix: replace raw ai_consent_required code with translatable message and consent prompt
- Centralize AI consent 403 responses in server-consent helpers - Return human-readable message + code + i18n errorKey from all AI APIs - Update dashboard and AI clients to detect code/errorKey and translate - Add consent.ai.requiredToast i18n keys (fr/en)
This commit is contained in:
@@ -3,7 +3,7 @@ import { NextRequest, NextResponse } from 'next/server'
|
||||
import { auth } from '@/auth'
|
||||
import { autoLabelCreationService } from '@/lib/ai/services'
|
||||
import { getAISettings } from '@/app/actions/ai-settings'
|
||||
import { hasUserAiConsent } from '@/lib/consent/server-consent'
|
||||
import { hasUserAiConsent, aiConsentForbiddenJson } from '@/lib/consent/server-consent'
|
||||
import { withAiQuota, handleQuotaHttpError } from '@/lib/ai-quota'
|
||||
|
||||
/**
|
||||
@@ -23,10 +23,7 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
// GDPR AI Consent check
|
||||
if (!(await hasUserAiConsent())) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: 'ai_consent_required' },
|
||||
{ status: 403 }
|
||||
)
|
||||
return aiConsentForbiddenJson()
|
||||
}
|
||||
|
||||
// Respect user's autoLabeling toggle
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { auth } from '@/auth'
|
||||
import { batchOrganizationService } from '@/lib/ai/services'
|
||||
import { hasUserAiConsent } from '@/lib/consent/server-consent'
|
||||
import { hasUserAiConsent, aiConsentForbiddenJson } from '@/lib/consent/server-consent'
|
||||
import { reserveUsageOrThrow, QuotaExceededError } from '@/lib/entitlements'
|
||||
|
||||
/**
|
||||
@@ -20,10 +20,7 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
// GDPR AI Consent check
|
||||
if (!(await hasUserAiConsent())) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: 'ai_consent_required' },
|
||||
{ status: 403 }
|
||||
)
|
||||
return aiConsentForbiddenJson()
|
||||
}
|
||||
|
||||
// Get language from request headers or body
|
||||
|
||||
@@ -2,7 +2,7 @@ import { NextRequest, NextResponse } from 'next/server'
|
||||
import { auth } from '@/auth'
|
||||
import { getAISettings } from '@/app/actions/ai-settings'
|
||||
import { describeImages } from '@/lib/ai/services/image-description.service'
|
||||
import { hasUserAiConsent } from '@/lib/consent/server-consent'
|
||||
import { hasUserAiConsent, aiConsentForbiddenResponse } from '@/lib/consent/server-consent'
|
||||
import { withAiQuota, handleQuotaHttpError } from '@/lib/ai-quota'
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
@@ -13,7 +13,7 @@ export async function POST(req: NextRequest) {
|
||||
}
|
||||
|
||||
if (!(await hasUserAiConsent())) {
|
||||
return NextResponse.json({ error: 'ai_consent_required' }, { status: 403 })
|
||||
return aiConsentForbiddenResponse()
|
||||
}
|
||||
|
||||
const userSettings = await getAISettings(session.user.id)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { auth } from '@/auth'
|
||||
import { memoryEchoService } from '@/lib/ai/services/memory-echo.service'
|
||||
import { hasUserAiConsent } from '@/lib/consent/server-consent'
|
||||
import { hasUserAiConsent, aiConsentForbiddenResponse } from '@/lib/consent/server-consent'
|
||||
|
||||
/**
|
||||
* GET /api/ai/echo/connections?noteId={id}&page={page}&limit={limit}
|
||||
@@ -19,10 +19,7 @@ export async function GET(req: NextRequest) {
|
||||
}
|
||||
|
||||
if (!(await hasUserAiConsent())) {
|
||||
return NextResponse.json(
|
||||
{ error: 'ai_consent_required' },
|
||||
{ status: 403 }
|
||||
)
|
||||
return aiConsentForbiddenResponse()
|
||||
}
|
||||
|
||||
// Get query parameters
|
||||
|
||||
@@ -3,7 +3,7 @@ import { auth } from '@/auth'
|
||||
import { getChatProvider } from '@/lib/ai/factory'
|
||||
import { getSystemConfig } from '@/lib/config'
|
||||
import prisma from '@/lib/prisma'
|
||||
import { hasUserAiConsent } from '@/lib/consent/server-consent'
|
||||
import { hasUserAiConsent, aiConsentForbiddenResponse } from '@/lib/consent/server-consent'
|
||||
import { withAiQuota, handleQuotaHttpError } from '@/lib/ai-quota'
|
||||
|
||||
/**
|
||||
@@ -22,7 +22,7 @@ export async function POST(req: NextRequest) {
|
||||
}
|
||||
|
||||
if (!(await hasUserAiConsent())) {
|
||||
return NextResponse.json({ error: 'ai_consent_required' }, { status: 403 })
|
||||
return aiConsentForbiddenResponse()
|
||||
}
|
||||
|
||||
const body = await req.json()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { auth } from '@/auth'
|
||||
import { memoryEchoService } from '@/lib/ai/services/memory-echo.service'
|
||||
import { hasUserAiConsent } from '@/lib/consent/server-consent'
|
||||
import { hasUserAiConsent, aiConsentForbiddenResponse } from '@/lib/consent/server-consent'
|
||||
import { reserveUsageOrThrow, QuotaExceededError } from '@/lib/entitlements'
|
||||
|
||||
/**
|
||||
@@ -21,10 +21,7 @@ export async function GET(req: NextRequest) {
|
||||
|
||||
// GDPR AI Consent check
|
||||
if (!(await hasUserAiConsent())) {
|
||||
return NextResponse.json(
|
||||
{ error: 'ai_consent_required' },
|
||||
{ status: 403 }
|
||||
)
|
||||
return aiConsentForbiddenResponse()
|
||||
}
|
||||
|
||||
// Get next insight (respects frequency limits)
|
||||
|
||||
@@ -2,7 +2,7 @@ import { NextRequest, NextResponse } from 'next/server'
|
||||
import { auth } from '@/auth'
|
||||
import { getSystemConfig } from '@/lib/config'
|
||||
import { getTagsProvider } from '@/lib/ai/factory'
|
||||
import { hasUserAiConsent } from '@/lib/consent/server-consent'
|
||||
import { hasUserAiConsent, aiConsentForbiddenResponse } from '@/lib/consent/server-consent'
|
||||
import { withAiQuota, handleQuotaHttpError } from '@/lib/ai-quota'
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
@@ -13,7 +13,7 @@ export async function POST(request: NextRequest) {
|
||||
}
|
||||
|
||||
if (!(await hasUserAiConsent())) {
|
||||
return NextResponse.json({ error: 'ai_consent_required' }, { status: 403 })
|
||||
return aiConsentForbiddenResponse()
|
||||
}
|
||||
|
||||
const { existingContent, resourceText, mode, language, format } = await request.json()
|
||||
|
||||
62
memento-note/app/api/ai/notebook-slides/route.ts
Normal file
62
memento-note/app/api/ai/notebook-slides/route.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { auth } from '@/auth'
|
||||
import { withAiQuota, handleQuotaHttpError } from '@/lib/ai-quota'
|
||||
import { hasUserAiConsent, aiConsentForbiddenResponse } from '@/lib/consent/server-consent'
|
||||
import { generateSlideDeck } from '@/lib/ai/services/slide-deck-generator.service'
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const session = await auth()
|
||||
if (!session?.user?.id) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
if (!(await hasUserAiConsent())) {
|
||||
return aiConsentForbiddenResponse()
|
||||
}
|
||||
const userId = session.user.id
|
||||
|
||||
const body = await request.json()
|
||||
const { notebookId, theme, purpose, audience, slideCount, language } = body
|
||||
|
||||
if (!notebookId || typeof notebookId !== 'string') {
|
||||
return NextResponse.json({ error: 'notebookId is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
const intent =
|
||||
purpose || audience || slideCount
|
||||
? {
|
||||
purpose: purpose || 'auto',
|
||||
audience: audience || 'auto',
|
||||
slideCount: typeof slideCount === 'number' ? slideCount : undefined,
|
||||
template: 'auto' as const,
|
||||
}
|
||||
: null
|
||||
|
||||
const result = await withAiQuota(
|
||||
userId,
|
||||
'slide_generate',
|
||||
() =>
|
||||
generateSlideDeck({
|
||||
userId,
|
||||
sourceNotebookId: notebookId,
|
||||
theme: theme && theme !== 'auto' ? theme : null,
|
||||
lang: language === 'en' ? 'en' : 'fr',
|
||||
intent,
|
||||
}),
|
||||
{ lane: 'chat' },
|
||||
)
|
||||
|
||||
if (!result.success) {
|
||||
return NextResponse.json({ error: result.error || 'Failed to generate slides' }, { status: 500 })
|
||||
}
|
||||
|
||||
return NextResponse.json(result)
|
||||
} catch (error: unknown) {
|
||||
const quotaResp = handleQuotaHttpError(error)
|
||||
if (quotaResp) return quotaResp
|
||||
console.error('[NotebookSlides] Error:', error)
|
||||
const message = error instanceof Error ? error.message : 'Failed to generate slides'
|
||||
return NextResponse.json({ error: message }, { status: 500 })
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import { rateLimit } from '@/lib/rate-limit'
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { auth } from '@/auth'
|
||||
import { notebookSummaryService } from '@/lib/ai/services'
|
||||
import { hasUserAiConsent } from '@/lib/consent/server-consent'
|
||||
import { hasUserAiConsent, aiConsentForbiddenJson } from '@/lib/consent/server-consent'
|
||||
import { reserveUsageOrThrow, QuotaExceededError } from '@/lib/entitlements'
|
||||
|
||||
/**
|
||||
@@ -20,10 +20,7 @@ export async function POST(request: NextRequest) {
|
||||
}
|
||||
|
||||
if (!(await hasUserAiConsent())) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: 'ai_consent_required' },
|
||||
{ status: 403 }
|
||||
)
|
||||
return aiConsentForbiddenJson()
|
||||
}
|
||||
|
||||
const body = await request.json()
|
||||
|
||||
@@ -3,7 +3,7 @@ import { auth } from '@/auth'
|
||||
import { getSystemConfig } from '@/lib/config'
|
||||
import { getTagsProvider } from '@/lib/ai/factory'
|
||||
import { reserveUsageOrThrow, QuotaExceededError } from '@/lib/entitlements'
|
||||
import { hasUserAiConsent } from '@/lib/consent/server-consent'
|
||||
import { hasUserAiConsent, aiConsentForbiddenResponse } from '@/lib/consent/server-consent'
|
||||
|
||||
export type PersonaId = 'engineer' | 'financial' | 'customer' | 'skeptic' | 'optimist'
|
||||
|
||||
@@ -70,7 +70,7 @@ export async function POST(request: NextRequest) {
|
||||
}
|
||||
|
||||
if (!(await hasUserAiConsent())) {
|
||||
return NextResponse.json({ error: 'ai_consent_required' }, { status: 403 })
|
||||
return aiConsentForbiddenResponse()
|
||||
}
|
||||
|
||||
const { content, title, personaId } = await request.json()
|
||||
|
||||
@@ -3,7 +3,7 @@ import { auth } from '@/auth'
|
||||
import { paragraphRefactorService } from '@/lib/ai/services/paragraph-refactor.service'
|
||||
import { getAISettings } from '@/app/actions/ai-settings'
|
||||
import { reserveUsageOrThrow, QuotaExceededError } from '@/lib/entitlements'
|
||||
import { hasUserAiConsent } from '@/lib/consent/server-consent'
|
||||
import { hasUserAiConsent, aiConsentForbiddenResponse } from '@/lib/consent/server-consent'
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
@@ -14,7 +14,7 @@ export async function POST(request: NextRequest) {
|
||||
}
|
||||
|
||||
if (!(await hasUserAiConsent())) {
|
||||
return NextResponse.json({ error: 'ai_consent_required' }, { status: 403 })
|
||||
return aiConsentForbiddenResponse()
|
||||
}
|
||||
|
||||
// Respect user's paragraphRefactor toggle (Assistant IA)
|
||||
|
||||
@@ -5,7 +5,7 @@ import { getSystemConfig } from '@/lib/config'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
import { auth } from '@/auth'
|
||||
import { reserveUsageOrThrow, QuotaExceededError, QuotaServiceUnavailableError } from '@/lib/entitlements'
|
||||
import { hasUserAiConsent } from '@/lib/consent/server-consent'
|
||||
import { hasUserAiConsent, aiConsentForbiddenResponse } from '@/lib/consent/server-consent'
|
||||
|
||||
export const maxDuration = 30
|
||||
|
||||
@@ -41,10 +41,7 @@ export async function POST(req: Request) {
|
||||
}
|
||||
|
||||
if (!(await hasUserAiConsent())) {
|
||||
return new Response(JSON.stringify({ error: 'ai_consent_required' }), {
|
||||
status: 403,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
return aiConsentForbiddenResponse()
|
||||
}
|
||||
const userId = session.user.id
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { auth } from '@/auth'
|
||||
import { notebookSuggestionService } from '@/lib/ai/services/notebook-suggestion.service'
|
||||
import { hasUserAiConsent } from '@/lib/consent/server-consent'
|
||||
import { hasUserAiConsent, aiConsentForbiddenResponse } from '@/lib/consent/server-consent'
|
||||
import { withAiQuota, handleQuotaHttpError } from '@/lib/ai-quota'
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
@@ -13,7 +13,7 @@ export async function POST(req: NextRequest) {
|
||||
const userId = session.user.id
|
||||
|
||||
if (!(await hasUserAiConsent())) {
|
||||
return NextResponse.json({ error: 'ai_consent_required' }, { status: 403 })
|
||||
return aiConsentForbiddenResponse()
|
||||
}
|
||||
|
||||
const body = await req.json()
|
||||
|
||||
@@ -6,7 +6,7 @@ import { runLaneWithBillingUser, willUseByokForLane } from '@/lib/ai/provider-fo
|
||||
import { getSystemConfig } from '@/lib/config';
|
||||
import { z } from 'zod';
|
||||
import { checkEntitlementOrThrow, QuotaExceededError, QuotaServiceUnavailableError } from '@/lib/entitlements';
|
||||
import { hasUserAiConsent } from '@/lib/consent/server-consent';
|
||||
import { hasUserAiConsent, aiConsentForbiddenResponse } from '@/lib/consent/server-consent';
|
||||
|
||||
import { getAISettings } from '@/app/actions/ai-settings';
|
||||
|
||||
@@ -25,7 +25,7 @@ export async function POST(req: NextRequest) {
|
||||
|
||||
// GDPR AI Consent check
|
||||
if (!(await hasUserAiConsent())) {
|
||||
return NextResponse.json({ error: 'ai_consent_required' }, { status: 403 });
|
||||
return aiConsentForbiddenResponse();
|
||||
}
|
||||
|
||||
const userSettings = await getAISettings(session.user.id);
|
||||
|
||||
@@ -7,7 +7,7 @@ import { getAISettings } from '@/app/actions/ai-settings'
|
||||
import { reserveAiUsageOrThrow } from '@/lib/ai-quota'
|
||||
import { QuotaExceededError, QuotaServiceUnavailableError } from '@/lib/entitlements'
|
||||
import { z } from 'zod'
|
||||
import { hasUserAiConsent } from '@/lib/consent/server-consent'
|
||||
import { hasUserAiConsent, aiConsentForbiddenResponse } from '@/lib/consent/server-consent'
|
||||
|
||||
const requestSchema = z.object({
|
||||
content: z.string().min(1, "Le contenu ne peut pas être vide"),
|
||||
@@ -37,7 +37,7 @@ export async function POST(req: NextRequest) {
|
||||
|
||||
// GDPR AI Consent check
|
||||
if (!(await hasUserAiConsent())) {
|
||||
return NextResponse.json({ error: 'ai_consent_required' }, { status: 403 })
|
||||
return aiConsentForbiddenResponse()
|
||||
}
|
||||
|
||||
const settings = await getAISettings(session.user.id)
|
||||
|
||||
@@ -2,7 +2,7 @@ import { NextRequest, NextResponse } from 'next/server'
|
||||
import { auth } from '@/auth'
|
||||
import { getChatProvider } from '@/lib/ai/factory'
|
||||
import { getSystemConfig } from '@/lib/config'
|
||||
import { hasUserAiConsent } from '@/lib/consent/server-consent'
|
||||
import { hasUserAiConsent, aiConsentForbiddenResponse } from '@/lib/consent/server-consent'
|
||||
import { withAiQuota, handleQuotaHttpError } from '@/lib/ai-quota'
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
@@ -14,7 +14,7 @@ export async function POST(request: NextRequest) {
|
||||
}
|
||||
|
||||
if (!(await hasUserAiConsent())) {
|
||||
return NextResponse.json({ error: 'ai_consent_required' }, { status: 403 })
|
||||
return aiConsentForbiddenResponse()
|
||||
}
|
||||
|
||||
const { text } = await request.json()
|
||||
|
||||
@@ -2,7 +2,7 @@ import { NextRequest, NextResponse } from 'next/server'
|
||||
import { auth } from '@/auth'
|
||||
import { getTagsProvider } from '@/lib/ai/factory'
|
||||
import { getSystemConfig } from '@/lib/config'
|
||||
import { hasUserAiConsent } from '@/lib/consent/server-consent'
|
||||
import { hasUserAiConsent, aiConsentForbiddenResponse } from '@/lib/consent/server-consent'
|
||||
import { reserveUsageOrThrow, QuotaExceededError } from '@/lib/entitlements'
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
@@ -13,7 +13,7 @@ export async function POST(request: NextRequest) {
|
||||
}
|
||||
|
||||
if (!(await hasUserAiConsent())) {
|
||||
return NextResponse.json({ error: 'ai_consent_required' }, { status: 403 })
|
||||
return aiConsentForbiddenResponse()
|
||||
}
|
||||
|
||||
const { text, targetLanguage } = await request.json()
|
||||
|
||||
Reference in New Issue
Block a user