The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"name": "Firestore \uc815\uc624 \uc694\uc57d \ub9ac\ud3ec\ud2b8 (Gemini)",
"nodes": [
{
"parameters": {
"triggerTimes": {
"item": [
{
"mode": "everyDay",
"hour": 12,
"minute": 0
}
]
},
"timezone": "Asia/Seoul"
},
"id": "cron-trigger",
"name": "Cron (\ub9e4\uc77c 12:00 KST)",
"type": "n8n-nodes-base.cron",
"typeVersion": 1,
"position": [
260,
260
]
},
{
"parameters": {
"functionCode": "// \uc624\ub298 00:00 KST ~ \ud604\uc7ac(\uc815\uc624) \uc2dc\uac04 \ubc94\uc704 \uacc4\uc0b0\nconst now = new Date();\nconst y = now.getFullYear();\nconst m = now.getMonth();\nconst d = now.getDate();\nconst start = new Date(y, m, d, 0, 0, 0);\nconst end = now;\n\n// \uc5b4\uc81c \uac19\uc740 \uc2dc\uac04\ub300\ub3c4 \uacc4\uc0b0 (\ube44\uad50\uc6a9)\nconst yesterday = new Date(start);\nyesterday.setDate(yesterday.getDate() - 1);\nconst yesterdayEnd = new Date(end);\nyesterdayEnd.setDate(yesterdayEnd.getDate() - 1);\n\nreturn [{\n json: {\n startISO: start.toISOString(),\n endISO: end.toISOString(),\n yesterdayStartISO: yesterday.toISOString(),\n yesterdayEndISO: yesterdayEnd.toISOString(),\n dateRange: `${start.toISOString().split('T')[0]} 00:00 ~ ${end.toISOString().split('T')[0]} 12:00`\n }\n}];"
},
"id": "time-calculator",
"name": "\uc2dc\uac04 \ubc94\uc704 \uacc4\uc0b0",
"type": "n8n-nodes-base.function",
"typeVersion": 2,
"position": [
520,
260
]
},
{
"parameters": {
"operation": "getAll",
"projectId": "what2eat-e21e3",
"collection": "reports",
"query": {
"queryFieldsUi": {
"queryFieldsValues": [
{
"field": "created_at",
"operator": ">=",
"value": "={{$json.startISO}}"
},
{
"field": "created_at",
"operator": "<=",
"value": "={{$json.endISO}}"
}
]
}
},
"limit": 1000
},
"id": "firestore-today",
"name": "Firestore: \uc624\ub298 \ub370\uc774\ud130 \uc870\ud68c",
"type": "n8n-nodes-base.googleFirebaseCloudFirestore",
"typeVersion": 1,
"credentials": {
"googleFirebaseCloudFirestoreOAuth2Api": {
"name": "<your credential>"
}
},
"position": [
780,
200
]
},
{
"parameters": {
"operation": "getAll",
"projectId": "what2eat-e21e3",
"collection": "reports",
"query": {
"queryFieldsUi": {
"queryFieldsValues": [
{
"field": "created_at",
"operator": ">=",
"value": "={{$json.yesterdayStartISO}}"
},
{
"field": "created_at",
"operator": "<=",
"value": "={{$json.yesterdayEndISO}}"
}
]
}
},
"limit": 1000
},
"id": "firestore-yesterday",
"name": "Firestore: \uc5b4\uc81c \ub370\uc774\ud130 \uc870\ud68c",
"type": "n8n-nodes-base.googleFirebaseCloudFirestore",
"typeVersion": 1,
"credentials": {
"googleFirebaseCloudFirestoreOAuth2Api": {
"name": "<your credential>"
}
},
"position": [
780,
320
]
},
{
"parameters": {
"functionCode": "// \uc624\ub298\uacfc \uc5b4\uc81c \ub370\uc774\ud130\ub97c \uc9d1\uacc4\ud558\uace0 \ube44\uad50 \ubd84\uc11d\uc6a9 \ud14d\uc2a4\ud2b8 \uc0dd\uc131\nconst todayData = $input.first().json;\nconst yesterdayData = $input.last().json;\n\n// \uc624\ub298 \ub370\uc774\ud130 \uc9d1\uacc4\nconst todayItems = todayData || [];\nconst todayByCategory = {};\nlet todayTotalValue = 0;\n\nfor (const item of todayItems) {\n const category = item.category || '\uae30\ud0c0';\n const value = Number(item.value || 0);\n \n if (!todayByCategory[category]) {\n todayByCategory[category] = { count: 0, sum: 0, items: [] };\n }\n \n todayByCategory[category].count += 1;\n todayByCategory[category].sum += value;\n todayByCategory[category].items.push({\n title: item.title || '\uc81c\ubaa9 \uc5c6\uc74c',\n value: value,\n created_at: item.created_at\n });\n todayTotalValue += value;\n}\n\n// \uc5b4\uc81c \ub370\uc774\ud130 \uc9d1\uacc4\nconst yesterdayItems = yesterdayData || [];\nconst yesterdayByCategory = {};\nlet yesterdayTotalValue = 0;\n\nfor (const item of yesterdayItems) {\n const category = item.category || '\uae30\ud0c0';\n const value = Number(item.value || 0);\n \n if (!yesterdayByCategory[category]) {\n yesterdayByCategory[category] = { count: 0, sum: 0 };\n }\n \n yesterdayByCategory[category].count += 1;\n yesterdayByCategory[category].sum += value;\n yesterdayTotalValue += value;\n}\n\n// \uac01 \uce74\ud14c\uace0\ub9ac\ubcc4 \uc0c1\uc704 \ud56d\ubaa9 \uc815\ub82c\nfor (const category of Object.keys(todayByCategory)) {\n todayByCategory[category].items.sort((a, b) => b.value - a.value);\n todayByCategory[category].top5 = todayByCategory[category].items.slice(0, 5);\n}\n\n// \uc804\uccb4 \uc0c1\uc704 \ud56d\ubaa9\nconst allTodayItems = [];\nfor (const categoryInfo of Object.values(todayByCategory)) {\n allTodayItems.push(...categoryInfo.items);\n}\nallTodayItems.sort((a, b) => b.value - a.value);\n\n// \ubcc0\ud654\uc728 \uacc4\uc0b0\nconst valueChangePercent = yesterdayTotalValue > 0 ? \n ((todayTotalValue - yesterdayTotalValue) / yesterdayTotalValue * 100).toFixed(1) : 0;\n\n// Gemini\uc5d0 \uc804\ub2ec\ud560 \ubd84\uc11d \ud14d\uc2a4\ud2b8 \uc0dd\uc131\nconst analysisText = `\ud83d\udcca **Firestore \ub370\uc774\ud130 \ubd84\uc11d \uc694\uccad**\n\n**\uae30\uac04**: ${$json.dateRange}\n**\ucd1d \ubb38\uc11c \uc218**: ${todayItems.length}\uac1c (\uc5b4\uc81c: ${yesterdayItems.length}\uac1c)\n**\ucd1d \uac12**: ${todayTotalValue.toFixed(2)} (\uc5b4\uc81c: ${yesterdayTotalValue.toFixed(2)}, \ubcc0\ud654: ${valueChangePercent}%)\n\n**\uce74\ud14c\uace0\ub9ac\ubcc4 \uc9d1\uacc4 (\uc624\ub298)**:\n` +\nObject.entries(todayByCategory).map(([category, info]) => {\n const yesterdayInfo = yesterdayByCategory[category] || { count: 0, sum: 0 };\n const countChange = yesterdayInfo.count > 0 ? \n ((info.count - yesterdayInfo.count) / yesterdayInfo.count * 100).toFixed(1) : 'N/A';\n const valueChange = yesterdayInfo.sum > 0 ? \n ((info.sum - yesterdayInfo.sum) / yesterdayInfo.sum * 100).toFixed(1) : 'N/A';\n \n return `- **${category}**: \uac1c\uc218=${info.count} (${countChange}%), \ud569\uacc4=${info.sum.toFixed(2)} (${valueChange}%)\\n \uc0c1\uc7045: ${info.top5.map(item => `${item.title}(${item.value})`).join(', ')}`;\n}).join('\\n') +\n`\\n\\n**\uc804\uccb4 \uc0c1\uc704 10\uac1c \ud56d\ubaa9**:\\n` +\nallTodayItems.slice(0, 10).map((item, i) => `${i+1}. ${item.title}: ${item.value}`).join('\\n') +\n`\\n\\n**\ud2b9\uc774\uc0ac\ud56d**:\\n- \ucd1d \uac12 \ubcc0\ud654: ${valueChangePercent}%\\n- \uac00\uc7a5 \ud65c\ubc1c\ud55c \uce74\ud14c\uace0\ub9ac: ${Object.entries(todayByCategory).sort((a,b) => b[1].count - a[1].count)[0]?.[0] || 'N/A'}\\n- \ucd5c\uace0\uac12 \ud56d\ubaa9: ${allTodayItems[0]?.title || 'N/A'} (${allTodayItems[0]?.value || 0})`;\n\nreturn [{\n json: {\n analysisText,\n todayData: {\n totalCount: todayItems.length,\n totalValue: todayTotalValue,\n categories: todayByCategory,\n topItems: allTodayItems.slice(0, 10)\n },\n yesterdayData: {\n totalCount: yesterdayItems.length,\n totalValue: yesterdayTotalValue\n },\n comparison: {\n valueChangePercent: parseFloat(valueChangePercent),\n countChangePercent: yesterdayItems.length > 0 ? \n ((todayItems.length - yesterdayItems.length) / yesterdayItems.length * 100).toFixed(1) : 'N/A'\n }\n }\n}];"
},
"id": "data-aggregator",
"name": "\ub370\uc774\ud130 \uc9d1\uacc4 & \ubd84\uc11d \ud14d\uc2a4\ud2b8 \uc0dd\uc131",
"type": "n8n-nodes-base.function",
"typeVersion": 2,
"position": [
1040,
260
]
},
{
"parameters": {
"model": "gemini-pro",
"options": {
"temperature": 0.2,
"maxTokens": 2000
},
"systemMessage": "\ub2f9\uc2e0\uc740 \ub370\uc774\ud130 \ubd84\uc11d \uc804\ubb38\uac00\uc785\ub2c8\ub2e4. Firestore \ub370\uc774\ud130\ub97c \ubd84\uc11d\ud558\uc5ec \uba85\ud655\ud558\uace0 \uc2e4\ud589 \uac00\ub2a5\ud55c \uc778\uc0ac\uc774\ud2b8\uac00 \ud3ec\ud568\ub41c \ud55c\uad6d\uc5b4 \ub9ac\ud3ec\ud2b8\ub97c \uc791\uc131\ud574\uc8fc\uc138\uc694. \ub9c8\ud06c\ub2e4\uc6b4 \ud615\uc2dd\uc744 \uc0ac\uc6a9\ud558\uace0, \uad6c\uccb4\uc801\uc778 \uc218\uce58\uc640 \ubcc0\ud654\uc728\uc744 \ud3ec\ud568\ud558\uc5ec \uc804\ubb38\uc801\uc778 \ubd84\uc11d\uc744 \uc81c\uacf5\ud574\uc8fc\uc138\uc694.",
"text": "={{$json.analysisText}}\n\n\uc704 \ub370\uc774\ud130\ub97c \ubc14\ud0d5\uc73c\ub85c \ub2e4\uc74c\uc744 \ud3ec\ud568\ud55c \uc815\uc624 \uc694\uc57d \ub9ac\ud3ec\ud2b8\ub97c \uc791\uc131\ud574\uc8fc\uc138\uc694:\n1. \uc8fc\uc694 \ud2b8\ub80c\ub4dc \ubc0f \ubcc0\ud654\n2. \uce74\ud14c\uace0\ub9ac\ubcc4 \uc0c1\uc138 \ubd84\uc11d\n3. \ud2b9\uc774\uc0ac\ud56d \ubc0f \uc774\uc0c1 \uc9d5\ud6c4\n4. \uc2e4\ud589 \uac00\ub2a5\ud55c \uc778\uc0ac\uc774\ud2b8 3\uac00\uc9c0\n5. \ub0b4\uc77c\uc744 \uc704\ud55c \uad8c\uc7a5\uc0ac\ud56d\n\n\ub9c8\ud06c\ub2e4\uc6b4 \ud615\uc2dd\uc73c\ub85c \uc791\uc131\ud574\uc8fc\uc138\uc694."
},
"id": "gemini-analyzer",
"name": "Gemini AI \ubd84\uc11d",
"type": "n8n-nodes-base.googleGemini",
"typeVersion": 1,
"credentials": {
"googleGeminiApi": {
"name": "<your credential>"
}
},
"position": [
1300,
260
]
},
{
"parameters": {
"functionCode": "// \ucd5c\uc885 \ub9ac\ud3ec\ud2b8 \ud3ec\ub9f7\ud305\nconst geminiAnalysis = $json.text || $json.data || '\ubd84\uc11d \uacb0\uacfc\ub97c \uac00\uc838\uc62c \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.';\nconst aggregatedData = $('\ub370\uc774\ud130 \uc9d1\uacc4 & \ubd84\uc11d \ud14d\uc2a4\ud2b8 \uc0dd\uc131').first().json;\nconst timeInfo = $('\uc2dc\uac04 \ubc94\uc704 \uacc4\uc0b0').first().json;\n\nconst finalReport = `\ud83d\udcca **Firestore \uc815\uc624 \uc694\uc57d \ub9ac\ud3ec\ud2b8**\n\ud83d\udcc5 **\ubd84\uc11d \uae30\uac04**: ${timeInfo.dateRange}\n\ud83d\udcc8 **\ub370\uc774\ud130 \ud604\ud669**: \ucd1d ${aggregatedData.todayData.totalCount}\uac1c \ubb38\uc11c, \ucd1d \uac12 ${aggregatedData.todayData.totalValue.toFixed(2)}\n\ud83d\udcca **\uc804\uc77c \ub300\ube44**: \uac12 ${aggregatedData.comparison.valueChangePercent}% \ubcc0\ud654, \ubb38\uc11c \uc218 ${aggregatedData.comparison.countChangePercent}% \ubcc0\ud654\n\n---\n\n${geminiAnalysis}\n\n---\n\n\ud83d\udccb **\uc694\uc57d \ud1b5\uacc4**:\n- \ubd84\uc11d \uc2dc\uac04: ${new Date().toLocaleString('ko-KR', { timeZone: 'Asia/Seoul' })}\n- \ub370\uc774\ud130 \uc18c\uc2a4: Firestore \uceec\ub809\uc158 'reports'\n- \ubd84\uc11d \ub3c4\uad6c: Google Gemini AI`;\n\nreturn [{\n json: {\n finalReport,\n summary: {\n timestamp: new Date().toISOString(),\n dataCount: aggregatedData.todayData.totalCount,\n totalValue: aggregatedData.todayData.totalValue,\n changePercent: aggregatedData.comparison.valueChangePercent\n }\n }\n}];"
},
"id": "report-formatter",
"name": "\ucd5c\uc885 \ub9ac\ud3ec\ud2b8 \ud3ec\ub9f7\ud305",
"type": "n8n-nodes-base.function",
"typeVersion": 2,
"position": [
1560,
260
]
},
{
"parameters": {
"authentication": "accessToken",
"channel": "#daily-reports",
"text": "={{$json.finalReport}}",
"otherOptions": {
"thread_ts": "",
"reply_broadcast": false
}
},
"id": "slack-notification",
"name": "Slack \uc54c\ub9bc \uc804\uc1a1",
"type": "n8n-nodes-base.slack",
"typeVersion": 2,
"credentials": {
"slackApi": {
"name": "<your credential>"
}
},
"position": [
1820,
260
]
},
{
"parameters": {
"authentication": "accessToken",
"channel": "#daily-reports",
"text": "\u274c **Firestore \uc815\uc624 \uc694\uc57d \uc2e4\ud328**\n\n\uc6cc\ud06c\ud50c\ub85c\uc6b0 \uc2e4\ud589 \uc911 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.\n\n**\uc624\ub958 \uc815\ubcf4**:\n- \uc2dc\uac04: {{new Date().toLocaleString('ko-KR', { timeZone: 'Asia/Seoul' })}}\n- \ub178\ub4dc: {{$node.name}}\n- \uc624\ub958: {{$json.error.message || '\uc54c \uc218 \uc5c6\ub294 \uc624\ub958'}}\n\n\uad00\ub9ac\uc790\uc5d0\uac8c \ubb38\uc758\ud574\uc8fc\uc138\uc694.",
"otherOptions": {}
},
"id": "slack-error",
"name": "Slack \uc624\ub958 \uc54c\ub9bc",
"type": "n8n-nodes-base.slack",
"typeVersion": 2,
"credentials": {
"slackApi": {
"name": "<your credential>"
}
},
"position": [
1300,
400
]
}
],
"connections": {
"Cron (\ub9e4\uc77c 12:00 KST)": {
"main": [
[
{
"node": "\uc2dc\uac04 \ubc94\uc704 \uacc4\uc0b0",
"type": "main",
"index": 0
}
]
]
},
"\uc2dc\uac04 \ubc94\uc704 \uacc4\uc0b0": {
"main": [
[
{
"node": "Firestore: \uc624\ub298 \ub370\uc774\ud130 \uc870\ud68c",
"type": "main",
"index": 0
},
{
"node": "Firestore: \uc5b4\uc81c \ub370\uc774\ud130 \uc870\ud68c",
"type": "main",
"index": 0
}
]
]
},
"Firestore: \uc624\ub298 \ub370\uc774\ud130 \uc870\ud68c": {
"main": [
[
{
"node": "\ub370\uc774\ud130 \uc9d1\uacc4 & \ubd84\uc11d \ud14d\uc2a4\ud2b8 \uc0dd\uc131",
"type": "main",
"index": 0
}
]
]
},
"Firestore: \uc5b4\uc81c \ub370\uc774\ud130 \uc870\ud68c": {
"main": [
[
{
"node": "\ub370\uc774\ud130 \uc9d1\uacc4 & \ubd84\uc11d \ud14d\uc2a4\ud2b8 \uc0dd\uc131",
"type": "main",
"index": 0
}
]
]
},
"\ub370\uc774\ud130 \uc9d1\uacc4 & \ubd84\uc11d \ud14d\uc2a4\ud2b8 \uc0dd\uc131": {
"main": [
[
{
"node": "Gemini AI \ubd84\uc11d",
"type": "main",
"index": 0
}
]
]
},
"Gemini AI \ubd84\uc11d": {
"main": [
[
{
"node": "\ucd5c\uc885 \ub9ac\ud3ec\ud2b8 \ud3ec\ub9f7\ud305",
"type": "main",
"index": 0
}
]
]
},
"\ucd5c\uc885 \ub9ac\ud3ec\ud2b8 \ud3ec\ub9f7\ud305": {
"main": [
[
{
"node": "Slack \uc54c\ub9bc \uc804\uc1a1",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
},
"staticData": null,
"tags": [
{
"createdAt": "2024-01-01T00:00:00.000Z",
"updatedAt": "2024-01-01T00:00:00.000Z",
"id": "firestore-summary",
"name": "Firestore Summary"
}
],
"triggerCount": 1,
"updatedAt": "2024-01-01T00:00:00.000Z",
"versionId": "1"
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
googleFirebaseCloudFirestoreOAuth2ApigoogleGeminiApislackApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Firestore 정오 요약 리포트 (Gemini). Uses googleFirebaseCloudFirestore, googleGemini, slack. Scheduled trigger; 9 nodes.
Source: https://github.com/LearningnRunning/TIL/blob/69d748ad3e15ae05c1da0e9fe0d92334067e4bb2/workflow/firestore-summary/n8n_workflow.json — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
The AI Blog Creator with Gemini, Replicate Image, Supabase Publishing & Slack is a fully automated content generation and publishing workflow designed for modern marketing and SaaS teams. It automatic
Automatically monitors restaurant ratings on Google Places daily, detects meaningful changes, uses Google Gemini AI to diagnose the root cause from real customer reviews, and delivers smart alerts to
For team leads, HR, and managers who want to monitor the emotional tone and morale of their teams based on message sentiment. Trigger: Runs every Monday at 9 AM. Config: Defines your Teams and Slack c
This workflow automatically generates a 6-slide Instagram carousel about tech topics(you can edit the prompt how you like to make it for best for your usecase) every day at noon(you can set whatever t
AI Institutional Stock Valuation Engine with Risk Scoring & Scenario Targets