feat: Memento avec dates, Markdown, reminders et auth

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
This commit is contained in:
2026-01-04 16:04:24 +01:00
parent 2de2958b7a
commit f0b41572bc
25 changed files with 4220 additions and 142 deletions

282
n8n-tech-news-workflow.json Normal file
View File

@@ -0,0 +1,282 @@
{
"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"
}