Files
nav-carte/server/utils/notify.ts
T
JulesandClaude Opus 5.5 09d1dc9d5f feat(aep): M1 — formulaire /proposer assoupli (liens + texte libre + chip)
Le contributeur colle un lien (ou plusieurs) et explique en texte libre
pourquoi c'est pertinent, sans template ni minimum de caractères. Le type
devient une suggestion optionnelle (chips). Les 5 formulaires typés restent
disponibles derrière « Mode détaillé » (replié par défaut).

- utils/submitLibre.ts : schéma Zod partagé client/serveur + mapping vers les
  colonnes NocoDB existantes (aucun changement de schéma) — table orgas par
  défaut, table ressources_references si le chip « Références » est choisi.
- components/FormLibre.vue : liens dynamiques (1-10), texte libre, chips,
  email optionnel, honeypot.
- server/api/submit/index.post.ts : branche mode=libre en amont du schéma
  typé existant ; email Jules remplacé par ntfy (server/utils/notify.ts) —
  jamais l'email ni le texte du contributeur dans la notif.
- scripts/test-submit-libre.mjs : 6 cas contre le vrai schéma Zod (via jiti).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 15:57:11 +02:00

80 lines
2.4 KiB
TypeScript

/**
* Notification ntfy.sh — remplace Resend (abandonné le 15/07, domaine non vérifié).
* POST https://ntfy.sh/${NTFY_TOPIC}
*
* RÈGLE : un topic ntfy est une URL, pas un coffre — le message ne contient
* JAMAIS l'email du contributeur ni le texte libre soumis. Seulement des
* métadonnées de modération (id NocoDB, nom suggéré, type, nb de liens).
*
* Si NTFY_TOPIC est absent : log et continue (pas d'erreur bloquante).
*/
export interface NtfyNotifyInput {
title: string
message: string
/** Priorité ntfy 1-5 (3 = défaut) */
priority?: number
tags?: string[]
/** Lien cliqué depuis la notif (ex : NocoDB) */
clickUrl?: string
}
export async function notifyNtfy(topic: string | undefined, input: NtfyNotifyInput): Promise<void> {
if (!topic) {
console.warn('[notify] NTFY_TOPIC absent — notification skippée:', input.title)
return
}
try {
const headers: Record<string, string> = {
Title: encodeHeaderValue(input.title),
Priority: String(input.priority ?? 3),
}
if (input.tags?.length) headers.Tags = input.tags.join(',')
if (input.clickUrl) headers.Click = input.clickUrl
const res = await fetch(`https://ntfy.sh/${topic}`, {
method: 'POST',
headers,
body: input.message,
})
if (!res.ok) {
console.warn('[notify] ntfy erreur HTTP:', res.status, await res.text())
}
} catch (e: any) {
console.warn('[notify] ntfy exception:', e?.message ?? e)
}
}
/**
* Construit le message de notification pour une nouvelle soumission.
* N'inclut jamais l'email ni le texte libre du contributeur.
*/
export function buildSubmissionNotif(input: {
submissionId: number | string | null
nomSuggere: string
typeSuggere: string
nbLiens: number
nocodbAdminUrl?: string
}): NtfyNotifyInput {
const { submissionId, nomSuggere, typeSuggere, nbLiens, nocodbAdminUrl } = input
return {
title: '[AEP] Nouvelle fiche soumise',
message: [
`Id NocoDB : ${submissionId ?? '—'}`,
`Nom suggéré : ${nomSuggere}`,
`Type suggéré : ${typeSuggere}`,
`Liens : ${nbLiens}`,
].join('\n'),
tags: ['inbox_tray'],
clickUrl: nocodbAdminUrl,
}
}
function encodeHeaderValue(v: string): string {
// ntfy accepte l'UTF-8 dans les headers mais certains proxies sont capricieux
// avec les accents en header brut — on reste simple, ntfy gère nativement.
return v
}