feat(proposer): interface 5 onglets + soumission Références vers NocoDB
- Page /proposer avec 5 onglets (Écosystème/Réseau/Jobs/Outils/Références RAG) - 5 form components + form-styles.css partagé - Submit multi-type avec submission_type vers NocoDB - Server route references.post.ts réécrite pour NocoDB (table ressources_references) - redirect 301 /contribuer → /proposer - Suppression references-pending.json
This commit is contained in:
74
server/api/submit/reference.post.ts
Normal file
74
server/api/submit/reference.post.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { z } from 'zod'
|
||||
import { checkRateLimit } from '~/server/utils/rateLimit'
|
||||
|
||||
const ReferenceSchema = z.object({
|
||||
titre: z.string().min(3, 'Minimum 3 caractères').max(200, 'Maximum 200 caractères').trim(),
|
||||
auteur: z.string().min(2, 'Minimum 2 caractères').max(200, 'Maximum 200 caractères').trim(),
|
||||
url: z.string().url('URL invalide').optional().or(z.literal('')).transform(v => v || undefined),
|
||||
description: z.string().min(50, 'Minimum 50 caractères').max(1000, 'Maximum 1000 caractères').trim(),
|
||||
pertinence_rag: z.string().max(500, 'Maximum 500 caractères').optional().transform(v => v?.trim() || undefined),
|
||||
hashtags: z.string().max(300, 'Maximum 300 caractères').optional().transform(v => v?.trim() || undefined),
|
||||
submitted_by_email: z.string().email('Email invalide').optional().or(z.literal('')).transform(v => v || undefined),
|
||||
})
|
||||
|
||||
export type ReferenceInput = z.infer<typeof ReferenceSchema>
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const ip = getHeader(event, 'x-forwarded-for')?.split(',')[0].trim() || event.node.req.socket?.remoteAddress || '0.0.0.0'
|
||||
|
||||
const allowed = await checkRateLimit(ip, 'submit-reference', 3)
|
||||
if (!allowed) {
|
||||
throw createError({ statusCode: 429, statusMessage: 'Trop de soumissions. Réessaie demain.' })
|
||||
}
|
||||
|
||||
const body = await readBody(event)
|
||||
const parsed = ReferenceSchema.safeParse(body)
|
||||
|
||||
if (!parsed.success) {
|
||||
throw createError({
|
||||
statusCode: 422,
|
||||
statusMessage: 'Validation échouée',
|
||||
data: parsed.error.flatten().fieldErrors,
|
||||
})
|
||||
}
|
||||
|
||||
const data = parsed.data
|
||||
|
||||
const payload: Record<string, unknown> = {
|
||||
titre: data.titre,
|
||||
auteur: data.auteur,
|
||||
url: data.url || null,
|
||||
description: data.description,
|
||||
pertinence_rag: data.pertinence_rag || null,
|
||||
hashtags: data.hashtags ? data.hashtags.split(',').map(h => h.trim()).filter(Boolean).join(',') : null,
|
||||
submitted_by_email: data.submitted_by_email || null,
|
||||
moderation_status: 'pending',
|
||||
submitted_at: new Date().toISOString(),
|
||||
}
|
||||
|
||||
const config = useRuntimeConfig()
|
||||
const insertUrl = `${config.nocodbUrl}/api/v1/db/data/noco/${config.nocodbBase}/${config.referencesTableId}`
|
||||
|
||||
let insertedRecord: any
|
||||
try {
|
||||
insertedRecord = await $fetch(insertUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'xc-token': config.nocodbToken,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
})
|
||||
} catch (e: any) {
|
||||
console.error('[reference] Erreur NocoDB insert:', e?.message ?? e)
|
||||
throw createError({ statusCode: 502, statusMessage: 'Erreur serveur — réessaie dans quelques instants.' })
|
||||
}
|
||||
|
||||
const referenceId = insertedRecord?.Id ?? insertedRecord?.id ?? null
|
||||
|
||||
return {
|
||||
status: 201,
|
||||
referenceId,
|
||||
message: 'Ta référence est en attente de modération.',
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user