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:
@@ -1,8 +1,8 @@
|
||||
/**
|
||||
* POST /api/submit
|
||||
*
|
||||
* Soumission d'une nouvelle organisation par un utilisateur.
|
||||
* - Validation Zod côté serveur
|
||||
* Soumission multi-type (ecosysteme / reseau / job / outil).
|
||||
* - Validation Zod côté serveur (champs communs + par type)
|
||||
* - Rate limit Redis : 3 soumissions / IP / jour
|
||||
* - Géocodage Nominatim (optionnel — fallback silencieux)
|
||||
* - INSERT NocoDB : moderation_status=pending, ai_processed=false
|
||||
@@ -15,62 +15,49 @@
|
||||
import { z } from 'zod'
|
||||
import { checkRateLimit } from '~/server/utils/rateLimit'
|
||||
|
||||
// ── Schéma Zod ────────────────────────────────────────────────────────────────
|
||||
const SUBMISSION_TYPES = ['ecosysteme', 'reseau', 'job', 'outil'] as const
|
||||
|
||||
const FONCTIONS = [
|
||||
'Juridique',
|
||||
'Technique',
|
||||
'Économique',
|
||||
'Administratif',
|
||||
'Chantier',
|
||||
'Comptabilité',
|
||||
'Développement',
|
||||
'Formation',
|
||||
'Gestion d\'agence',
|
||||
'Santé mentale',
|
||||
'Juridique', 'Technique', 'Économique', 'Administratif', 'Chantier',
|
||||
'Comptabilité', 'Développement', 'Formation', 'Gestion d\'agence', 'Santé mentale',
|
||||
] as const
|
||||
|
||||
const ECHELLES = ['National', 'Régional', 'Local'] as const
|
||||
|
||||
const TERRITOIRES = [
|
||||
'Métropole',
|
||||
'Guadeloupe',
|
||||
'Martinique',
|
||||
'Guyane',
|
||||
'La Réunion',
|
||||
'Mayotte',
|
||||
'Métropole', 'Guadeloupe', 'Martinique', 'Guyane', 'La Réunion', 'Mayotte',
|
||||
] as const
|
||||
|
||||
const TYPES_PRINCIPAUX = ['agence', 'collectif', 'reseau', 'asso', 'ressource'] as const
|
||||
|
||||
const TYPES_PLATEFORME = ['mise-en-relation', 'appel-offre-public', 'communaute-pro'] as const
|
||||
|
||||
const CATEGORIES_OUTIL = ['simulateur', 'logiciel', 'guide', 'autre'] as const
|
||||
|
||||
export const SubmitSchema = z.object({
|
||||
// Champs communs
|
||||
nom: z.string().min(3, 'Minimum 3 caractères').max(150, 'Maximum 150 caractères').trim(),
|
||||
url: z
|
||||
.string()
|
||||
.url('URL invalide')
|
||||
.optional()
|
||||
.or(z.literal(''))
|
||||
.transform((v) => v || undefined),
|
||||
description_user: z
|
||||
.string()
|
||||
.min(50, 'Minimum 50 caractères')
|
||||
.max(500, 'Maximum 500 caractères')
|
||||
.trim(),
|
||||
echelle: z.enum(ECHELLES, { errorMap: () => ({ message: 'Échelle invalide' }) }),
|
||||
fonctions: z
|
||||
.array(z.enum(FONCTIONS))
|
||||
.min(1, 'Sélectionne au moins une fonction')
|
||||
.max(5, 'Maximum 5 fonctions'),
|
||||
territoire: z.enum(TERRITOIRES, { errorMap: () => ({ message: 'Territoire invalide' }) }),
|
||||
localisation_ville: z
|
||||
.string()
|
||||
.max(100)
|
||||
.optional()
|
||||
.transform((v) => v?.trim() || undefined),
|
||||
submitted_by_email: z
|
||||
.string()
|
||||
.email('Email invalide')
|
||||
.optional()
|
||||
.or(z.literal(''))
|
||||
.transform((v) => v || undefined),
|
||||
url: z.string().url('URL invalide').optional().or(z.literal('')).transform(v => v || undefined),
|
||||
description_user: z.string().min(50, 'Minimum 50 caractères').max(500, 'Maximum 500 caractères').trim(),
|
||||
submitted_by_email: z.string().email('Email invalide').optional().or(z.literal('')).transform(v => v || undefined),
|
||||
submission_type: z.enum(SUBMISSION_TYPES, { errorMap: () => ({ message: 'Type de soumission invalide' }) }),
|
||||
|
||||
// Champs écosystème
|
||||
echelle: z.enum(ECHELLES).optional(),
|
||||
fonctions: z.array(z.enum(FONCTIONS)).min(1).max(5).optional(),
|
||||
territoire: z.enum(TERRITOIRES).optional(),
|
||||
localisation_ville: z.string().max(100).optional().transform(v => v?.trim() || undefined),
|
||||
|
||||
// Champs réseau
|
||||
type_principal: z.enum(TYPES_PRINCIPAUX).optional(),
|
||||
projet_lien: z.string().url('URL invalide').optional().or(z.literal('')),
|
||||
projet_adresse: z.string().max(200).optional().transform(v => v?.trim() || undefined),
|
||||
|
||||
// Champs job
|
||||
type_plateforme: z.enum(TYPES_PLATEFORME).optional(),
|
||||
|
||||
// Champs outil
|
||||
categorie: z.enum(CATEGORIES_OUTIL).optional(),
|
||||
})
|
||||
|
||||
export type SubmitInput = z.infer<typeof SubmitSchema>
|
||||
@@ -148,22 +135,36 @@ export default defineEventHandler(async (event) => {
|
||||
const nocoBaseId = config.nocodbBase
|
||||
|
||||
const payload: Record<string, unknown> = {
|
||||
submission_type: data.submission_type,
|
||||
nom: data.nom,
|
||||
url: data.url || null,
|
||||
description_user: data.description_user,
|
||||
echelle: data.echelle,
|
||||
tags_fonction: data.fonctions.join(','),
|
||||
territoire: data.territoire,
|
||||
localisation_ville: data.localisation_ville || null,
|
||||
submitted_by_email: data.submitted_by_email || null,
|
||||
moderation_status: 'pending',
|
||||
scrape_status: data.url ? 'pending' : 'no_link',
|
||||
ai_processed: false,
|
||||
submitted_at: new Date().toISOString(),
|
||||
latitude,
|
||||
longitude,
|
||||
}
|
||||
|
||||
// Champs spécifiques selon le type
|
||||
if (data.submission_type === 'ecosysteme') {
|
||||
payload.echelle = data.echelle
|
||||
payload.tags_fonction = data.fonctions?.join(',')
|
||||
payload.territoire = data.territoire
|
||||
payload.localisation_ville = data.localisation_ville || null
|
||||
payload.scrape_status = data.url ? 'pending' : 'no_link'
|
||||
} else if (data.submission_type === 'reseau') {
|
||||
payload.type_principal = data.type_principal
|
||||
payload.projet_lien = data.projet_lien || null
|
||||
payload.projet_adresse = data.projet_adresse || null
|
||||
payload.localisation_ville = data.localisation_ville || null
|
||||
} else if (data.submission_type === 'job') {
|
||||
payload.type_plateforme = data.type_plateforme
|
||||
} else if (data.submission_type === 'outil') {
|
||||
payload.categorie = data.categorie
|
||||
}
|
||||
|
||||
// NocoDB v1 endpoint
|
||||
const insertUrl = `${nocodbUrl}/api/v1/db/data/noco/${nocoBaseId}/${orgTableId}`
|
||||
|
||||
|
||||
Reference in New Issue
Block a user