Tests Playwright validés ✅:
- Création de notes: OK
- Modification titre: OK
- Modification contenu: OK
- Markdown éditable avec preview: OK
Fonctionnalités:
- date-fns: dates relatives sur cards
- react-markdown + remark-gfm
- Markdown avec toggle edit/preview
- Recherche améliorée (titre/contenu/labels/checkItems)
- Reminder recurrence/location (schema)
- NextAuth.js + User/Account/Session
- userId dans Note (optionnel)
- 4 migrations créées
Ready for production + auth integration
283 lines
10 KiB
JSON
283 lines
10 KiB
JSON
{
|
||
"name": "Tech News to Memento via MCP",
|
||
"nodes": [
|
||
{
|
||
"parameters": {
|
||
"url": "https://feeds.feedburner.com/TechCrunch/",
|
||
"options": {}
|
||
},
|
||
"id": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
|
||
"name": "RSS Feed - Tech News",
|
||
"type": "n8n-nodes-base.rssFeedRead",
|
||
"typeVersion": 1,
|
||
"position": [250, 300]
|
||
},
|
||
{
|
||
"parameters": {
|
||
"jsCode": "// Prepare RSS items for AI analysis\nconst items = $input.all();\n\n// Format articles for AI\nconst articles = items.map((item, index) => ({\n index: index + 1,\n title: item.json.title,\n description: item.json.contentSnippet || item.json.description || '',\n link: item.json.link,\n pubDate: item.json.pubDate,\n categories: item.json.categories || []\n}));\n\n// Create prompt for AI\nconst prompt = `Analysez ces ${articles.length} actualités technologiques et sélectionnez les 2 articles les PLUS PERTINENTS et IMPORTANTS.\n\nCritères de sélection :\n- Innovation majeure ou rupture technologique\n- Impact significatif sur l'industrie tech\n- Actualité récente et d'importance\n- Éviter les articles marketing ou promotionnels\n- Privilégier les annonces concrètes\n\nArticles disponibles :\n${articles.map(a => `\n[${a.index}] ${a.title}\nDescription: ${a.description.substring(0, 300)}...\nCatégories: ${a.categories.join(', ')}\nLien: ${a.link}\n`).join('\\n---\\n')}\n\nRépondez UNIQUEMENT au format JSON suivant (rien d'autre) :\n{\n \"selected\": [\n {\n \"index\": <numéro de l'article>,\n \"reason\": \"<courte raison de la sélection en 1-2 phrases>\"\n },\n {\n \"index\": <numéro de l'article>,\n \"reason\": \"<courte raison de la sélection en 1-2 phrases>\"\n }\n ]\n}`;\n\nreturn [{\n json: {\n prompt,\n articles\n }\n}];"
|
||
},
|
||
"id": "2b3c4d5e-6f7a-8b9c-0d1e-2f3a4b5c6d7e",
|
||
"name": "Prepare AI Analysis",
|
||
"type": "n8n-nodes-base.code",
|
||
"typeVersion": 2,
|
||
"position": [450, 300]
|
||
},
|
||
{
|
||
"parameters": {
|
||
"model": "gpt-4o-mini",
|
||
"options": {
|
||
"temperature": 0.3,
|
||
"maxTokens": 500
|
||
},
|
||
"messages": {
|
||
"values": [
|
||
{
|
||
"role": "system",
|
||
"content": "Tu es un expert en analyse d'actualités technologiques. Ta mission est de sélectionner les 2 articles les plus pertinents et importants parmi une liste d'actualités. Tu dois être objectif, privilégier l'innovation et l'impact réel. Réponds UNIQUEMENT en JSON valide, sans markdown ni texte supplémentaire."
|
||
},
|
||
{
|
||
"role": "user",
|
||
"content": "={{ $json.prompt }}"
|
||
}
|
||
]
|
||
}
|
||
},
|
||
"id": "3c4d5e6f-7a8b-9c0d-1e2f-3a4b5c6d7e8f",
|
||
"name": "OpenAI - Select Best Articles",
|
||
"type": "@n8n/n8n-nodes-langchain.openAi",
|
||
"typeVersion": 1.3,
|
||
"position": [650, 300],
|
||
"credentials": {
|
||
"openAiApi": {
|
||
"id": "1",
|
||
"name": "OpenAI Account"
|
||
}
|
||
}
|
||
},
|
||
{
|
||
"parameters": {
|
||
"jsCode": "// Parse AI response and match with articles\nconst aiResponse = $input.first().json.message?.content || $input.first().json.text;\nconst articles = $('Prepare AI Analysis').first().json.articles;\n\ntry {\n // Extract JSON from response (handles markdown code blocks)\n let jsonStr = aiResponse.trim();\n if (jsonStr.startsWith('```json')) {\n jsonStr = jsonStr.replace(/```json\\n?/g, '').replace(/```\\n?/g, '');\n } else if (jsonStr.startsWith('```')) {\n jsonStr = jsonStr.replace(/```\\n?/g, '');\n }\n \n const selection = JSON.parse(jsonStr);\n \n // Get selected articles\n const selectedArticles = selection.selected.map(sel => {\n const article = articles[sel.index - 1];\n return {\n ...article,\n selectionReason: sel.reason\n };\n });\n \n return selectedArticles.map(article => ({ json: article }));\n \n} catch (error) {\n console.error('Error parsing AI response:', error);\n console.error('AI Response:', aiResponse);\n \n // Fallback: return first 2 articles\n return articles.slice(0, 2).map(article => ({ json: article }));\n}"
|
||
},
|
||
"id": "4d5e6f7a-8b9c-0d1e-2f3a-4b5c6d7e8f9a",
|
||
"name": "Parse Selection",
|
||
"type": "n8n-nodes-base.code",
|
||
"typeVersion": 2,
|
||
"position": [850, 300]
|
||
},
|
||
{
|
||
"parameters": {
|
||
"jsCode": "// Format note for creation\nconst article = $input.first().json;\n\n// Create rich note content\nconst noteContent = `📰 **${article.title}**\n\n🔍 **Pourquoi cet article ?**\n${article.selectionReason}\n\n📝 **Résumé :**\n${article.description}\n\n🔗 **Lien :** ${article.link}\n\n📅 **Publié le :** ${new Date(article.pubDate).toLocaleDateString('fr-FR', { \n year: 'numeric', \n month: 'long', \n day: 'numeric',\n hour: '2-digit',\n minute: '2-digit'\n})}\n\n🏷️ **Catégories :** ${article.categories.join(', ') || 'Non spécifié'}`;\n\nreturn [{\n json: {\n title: `📰 ${article.title.substring(0, 60)}${article.title.length > 60 ? '...' : ''}`,\n content: noteContent,\n color: 'blue',\n labels: ['Tech News', 'Auto-Generated', ...article.categories.slice(0, 3)],\n type: 'text'\n }\n}];"
|
||
},
|
||
"id": "5e6f7a8b-9c0d-1e2f-3a4b-5c6d7e8f9a0b",
|
||
"name": "Format Note",
|
||
"type": "n8n-nodes-base.code",
|
||
"typeVersion": 2,
|
||
"position": [1050, 300]
|
||
},
|
||
{
|
||
"parameters": {
|
||
"method": "POST",
|
||
"url": "http://localhost:3001/sse",
|
||
"authentication": "none",
|
||
"sendBody": true,
|
||
"specifyBody": "json",
|
||
"jsonBody": "={\n \"jsonrpc\": \"2.0\",\n \"id\": {{ $now.toUnixInteger() }},\n \"method\": \"tools/call\",\n \"params\": {\n \"name\": \"create_note\",\n \"arguments\": {\n \"title\": {{ $json.title | tojson }},\n \"content\": {{ $json.content | tojson }},\n \"color\": {{ $json.color | tojson }},\n \"type\": {{ $json.type | tojson }},\n \"labels\": {{ $json.labels | tojson }}\n }\n }\n}",
|
||
"options": {
|
||
"timeout": 10000
|
||
}
|
||
},
|
||
"id": "6f7a8b9c-0d1e-2f3a-4b5c-6d7e8f9a0b1c",
|
||
"name": "MCP - Create Note",
|
||
"type": "n8n-nodes-base.httpRequest",
|
||
"typeVersion": 4.2,
|
||
"position": [1250, 300]
|
||
},
|
||
{
|
||
"parameters": {
|
||
"assignments": {
|
||
"assignments": [
|
||
{
|
||
"id": "note_id",
|
||
"name": "note_id",
|
||
"value": "={{ $json.result?.content?.[0]?.text | fromjson | pick('id').id }}",
|
||
"type": "string"
|
||
},
|
||
{
|
||
"id": "note_title",
|
||
"name": "note_title",
|
||
"value": "={{ $json.result?.content?.[0]?.text | fromjson | pick('title').title }}",
|
||
"type": "string"
|
||
},
|
||
{
|
||
"id": "status",
|
||
"name": "status",
|
||
"value": "={{ $json.result ? 'success' : 'failed' }}",
|
||
"type": "string"
|
||
}
|
||
]
|
||
},
|
||
"options": {}
|
||
},
|
||
"id": "7a8b9c0d-1e2f-3a4b-5c6d-7e8f9a0b1c2d",
|
||
"name": "Extract Result",
|
||
"type": "n8n-nodes-base.set",
|
||
"typeVersion": 3.4,
|
||
"position": [1450, 300]
|
||
},
|
||
{
|
||
"parameters": {
|
||
"rule": {
|
||
"interval": [
|
||
{
|
||
"field": "hours",
|
||
"hoursInterval": 6
|
||
}
|
||
]
|
||
}
|
||
},
|
||
"id": "8b9c0d1e-2f3a-4b5c-6d7e-8f9a0b1c2d3e",
|
||
"name": "Schedule - Every 6 hours",
|
||
"type": "n8n-nodes-base.scheduleTrigger",
|
||
"typeVersion": 1.2,
|
||
"position": [50, 300]
|
||
},
|
||
{
|
||
"parameters": {
|
||
"assignments": {
|
||
"assignments": [
|
||
{
|
||
"id": "execution_time",
|
||
"name": "execution_time",
|
||
"value": "={{ $now.toISO() }}",
|
||
"type": "string"
|
||
},
|
||
{
|
||
"id": "notes_created",
|
||
"name": "notes_created",
|
||
"value": "={{ $('Extract Result').all().length }}",
|
||
"type": "number"
|
||
},
|
||
{
|
||
"id": "workflow_status",
|
||
"name": "workflow_status",
|
||
"value": "✅ Tech news workflow completed successfully",
|
||
"type": "string"
|
||
}
|
||
]
|
||
}
|
||
},
|
||
"id": "9c0d1e2f-3a4b-5c6d-7e8f-9a0b1c2d3e4f",
|
||
"name": "Summary",
|
||
"type": "n8n-nodes-base.set",
|
||
"typeVersion": 3.4,
|
||
"position": [1650, 300]
|
||
}
|
||
],
|
||
"connections": {
|
||
"Schedule - Every 6 hours": {
|
||
"main": [
|
||
[
|
||
{
|
||
"node": "RSS Feed - Tech News",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"RSS Feed - Tech News": {
|
||
"main": [
|
||
[
|
||
{
|
||
"node": "Prepare AI Analysis",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Prepare AI Analysis": {
|
||
"main": [
|
||
[
|
||
{
|
||
"node": "OpenAI - Select Best Articles",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"OpenAI - Select Best Articles": {
|
||
"main": [
|
||
[
|
||
{
|
||
"node": "Parse Selection",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Parse Selection": {
|
||
"main": [
|
||
[
|
||
{
|
||
"node": "Format Note",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Format Note": {
|
||
"main": [
|
||
[
|
||
{
|
||
"node": "MCP - Create Note",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"MCP - Create Note": {
|
||
"main": [
|
||
[
|
||
{
|
||
"node": "Extract Result",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
},
|
||
"Extract Result": {
|
||
"main": [
|
||
[
|
||
{
|
||
"node": "Summary",
|
||
"type": "main",
|
||
"index": 0
|
||
}
|
||
]
|
||
]
|
||
}
|
||
},
|
||
"pinData": {},
|
||
"settings": {
|
||
"executionOrder": "v1"
|
||
},
|
||
"staticData": null,
|
||
"tags": [
|
||
{
|
||
"createdAt": "2026-01-04T14:00:00.000Z",
|
||
"updatedAt": "2026-01-04T14:00:00.000Z",
|
||
"id": "tech-automation",
|
||
"name": "Tech Automation"
|
||
}
|
||
],
|
||
"triggerCount": 0,
|
||
"updatedAt": "2026-01-04T14:00:00.000Z",
|
||
"versionId": "1"
|
||
}
|