Migrate task persistence to SQLite and remove legacy JSON store
This commit is contained in:
parent
eaf01cf634
commit
301156a4ea
3
.gitignore
vendored
3
.gitignore
vendored
@ -23,6 +23,9 @@
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
data/*.db
|
||||
data/*.db-shm
|
||||
data/*.db-wal
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# Gantt Board
|
||||
|
||||
Task and sprint board built with Next.js + Zustand and file-backed API persistence.
|
||||
Task and sprint board built with Next.js + Zustand and SQLite-backed API persistence.
|
||||
|
||||
## Current Product Behavior
|
||||
|
||||
@ -45,7 +45,7 @@ Status behavior on drop:
|
||||
- Drop into backlog section: `status -> open`
|
||||
- `sprintId` is set/cleared based on destination
|
||||
|
||||
Changes persist through store sync to `data/tasks.json`.
|
||||
Changes persist through store sync to SQLite.
|
||||
|
||||
### Kanban drag and drop
|
||||
|
||||
@ -75,7 +75,7 @@ During drag, the active target column shows expanded status drop zones for clari
|
||||
|
||||
- Client state is managed with Zustand.
|
||||
- Persistence is done via `/api/tasks`.
|
||||
- API reads/writes `data/tasks.json` (single-file storage).
|
||||
- API reads/writes `data/tasks.db` (SQLite).
|
||||
|
||||
## Run locally
|
||||
|
||||
|
||||
509
data/tasks.json
509
data/tasks.json
@ -1,509 +0,0 @@
|
||||
{
|
||||
"projects": [
|
||||
{
|
||||
"id": "1",
|
||||
"name": "OpenClaw iOS",
|
||||
"description": "Main iOS app development",
|
||||
"color": "#8b5cf6",
|
||||
"createdAt": "2026-02-18T17:01:23.109Z"
|
||||
},
|
||||
{
|
||||
"id": "2",
|
||||
"name": "Web Projects",
|
||||
"description": "Web tools and dashboards",
|
||||
"color": "#3b82f6",
|
||||
"createdAt": "2026-02-18T17:01:23.109Z"
|
||||
},
|
||||
{
|
||||
"id": "3",
|
||||
"name": "Research",
|
||||
"description": "Experiments and learning",
|
||||
"color": "#10b981",
|
||||
"createdAt": "2026-02-18T17:01:23.109Z"
|
||||
}
|
||||
],
|
||||
"tasks": [
|
||||
{
|
||||
"id": "1",
|
||||
"title": "Redesign Gantt Board",
|
||||
"description": "Make it actually work with proper notes system",
|
||||
"type": "task",
|
||||
"status": "archived",
|
||||
"priority": "high",
|
||||
"projectId": "2",
|
||||
"sprintId": "sprint-1",
|
||||
"createdAt": "2026-02-18T17:01:23.109Z",
|
||||
"updatedAt": "2026-02-20T05:14:09.324Z",
|
||||
"comments": [
|
||||
{
|
||||
"id": "c1",
|
||||
"text": "Need 1-to-many notes, not one big text field",
|
||||
"createdAt": "2026-02-18T17:01:23.109Z",
|
||||
"author": "user"
|
||||
},
|
||||
{
|
||||
"id": "c2",
|
||||
"text": "Agreed - will rebuild with proper comment threads",
|
||||
"createdAt": "2026-02-18T17:01:23.109Z",
|
||||
"author": "assistant"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"ui",
|
||||
"rewrite",
|
||||
"Web Projects"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "2",
|
||||
"title": "MoodWeave App Idea - UPDATED",
|
||||
"projectId": "1",
|
||||
"status": "open",
|
||||
"priority": "high",
|
||||
"type": "idea",
|
||||
"comments": [],
|
||||
"tags": [
|
||||
"ios",
|
||||
"social",
|
||||
"OpenClaw iOS"
|
||||
],
|
||||
"createdAt": "2026-02-18T17:01:23.109Z",
|
||||
"updatedAt": "2026-02-20T05:02:47.264Z"
|
||||
},
|
||||
{
|
||||
"id": "3",
|
||||
"title": "Set up Gitea integration for code pushes",
|
||||
"description": "Create bot account on Gitea (192.168.1.128:3000) and configure git remotes for all OpenClaw projects.",
|
||||
"type": "task",
|
||||
"status": "done",
|
||||
"priority": "medium",
|
||||
"projectId": "2",
|
||||
"sprintId": "sprint-1",
|
||||
"createdAt": "2026-02-18T17:01:23.109Z",
|
||||
"updatedAt": "2026-02-18T17:01:23.109Z",
|
||||
"comments": [
|
||||
{
|
||||
"id": "c3",
|
||||
"text": "User has local Gitea at http://192.168.1.128:3000",
|
||||
"createdAt": "2026-02-18T17:01:23.109Z",
|
||||
"author": "assistant"
|
||||
},
|
||||
{
|
||||
"id": "c7",
|
||||
"text": "✅ All 3 repos created and pushed to Gitea: gantt-board, blog-backup, heartbeat-monitor",
|
||||
"createdAt": "2026-02-18T17:01:23.109Z",
|
||||
"author": "assistant"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"gitea",
|
||||
"git",
|
||||
"automation",
|
||||
"infrastructure",
|
||||
"Web Projects"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "4",
|
||||
"title": "Redesign Heartbeat Monitor to match UptimeRobot",
|
||||
"description": "Completely redesign the Heartbeat Monitor website to be a competitor to https://uptimerobot.com.",
|
||||
"type": "task",
|
||||
"status": "done",
|
||||
"priority": "high",
|
||||
"projectId": "2",
|
||||
"sprintId": "sprint-1",
|
||||
"createdAt": "2026-02-18T17:01:23.109Z",
|
||||
"updatedAt": "2026-02-18T17:01:23.109Z",
|
||||
"comments": [
|
||||
{
|
||||
"id": "c29",
|
||||
"text": "COMPLETED: Full rebuild with Next.js + shadcn/ui + Framer Motion. Dark OLED theme, glass-morphism cards, animated status indicators, sparkline visualizations, grid/list views, tooltips, progress bars. Production-grade at http://localhost:3005",
|
||||
"createdAt": "2026-02-18T17:01:23.109Z",
|
||||
"author": "assistant"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"ui",
|
||||
"ux",
|
||||
"redesign",
|
||||
"dashboard",
|
||||
"monitoring",
|
||||
"Web Projects"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "5",
|
||||
"title": "Fix Blog Backup links to be clickable",
|
||||
"description": "Make links in the Daily Digest clickable in the blog backup UI.",
|
||||
"type": "task",
|
||||
"status": "done",
|
||||
"priority": "medium",
|
||||
"projectId": "2",
|
||||
"sprintId": "sprint-1",
|
||||
"createdAt": "2026-02-18T17:01:23.109Z",
|
||||
"updatedAt": "2026-02-18T17:01:23.109Z",
|
||||
"comments": [
|
||||
{
|
||||
"id": "c41",
|
||||
"text": "COMPLETED: Fixed parseDigest to extract URLs from markdown links [Title](url) in title lines",
|
||||
"createdAt": "2026-02-18T17:01:23.109Z",
|
||||
"author": "assistant"
|
||||
},
|
||||
{
|
||||
"id": "c42",
|
||||
"text": "COMPLETED: Title is now the clickable link with external link icon on hover",
|
||||
"createdAt": "2026-02-18T17:01:23.109Z",
|
||||
"author": "assistant"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"blog",
|
||||
"ui",
|
||||
"markdown",
|
||||
"links",
|
||||
"Web Projects"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "6",
|
||||
"title": "Fix monitoring schedule - 2 of 3 sites are down",
|
||||
"description": "The cron job running every 10 minutes to check heartbeat website is failing.",
|
||||
"type": "bug",
|
||||
"status": "done",
|
||||
"priority": "urgent",
|
||||
"projectId": "2",
|
||||
"sprintId": "sprint-1",
|
||||
"createdAt": "2026-02-18T17:01:23.109Z",
|
||||
"updatedAt": "2026-02-18T17:01:23.109Z",
|
||||
"comments": [
|
||||
{
|
||||
"id": "c16",
|
||||
"text": "FIXED: Updated cron job with pkill cleanup before restart + 2s delay. Created backup script: monitor-restart.sh",
|
||||
"createdAt": "2026-02-18T17:01:23.109Z",
|
||||
"author": "assistant"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"monitoring",
|
||||
"cron",
|
||||
"bug",
|
||||
"infrastructure",
|
||||
"urgent",
|
||||
"Web Projects"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "7",
|
||||
"title": "Investigate root cause - why are websites dying?",
|
||||
"description": "Currently monitoring only treats the symptom (restart when down). Need to investigate what is actually killing the Next.js dev servers.",
|
||||
"type": "research",
|
||||
"status": "done",
|
||||
"priority": "high",
|
||||
"projectId": "2",
|
||||
"sprintId": "sprint-1",
|
||||
"createdAt": "2026-02-18T17:01:23.109Z",
|
||||
"updatedAt": "2026-02-18T17:01:23.109Z",
|
||||
"comments": [
|
||||
{
|
||||
"id": "c27",
|
||||
"text": "COMPLETED: Root cause analysis done. Primary suspect: Next.js dev server memory leaks. Secondary: SSH timeout, OOM killer, power mgmt. Full report: root-cause-analysis.md. Monitoring script deployed.",
|
||||
"createdAt": "2026-02-18T17:01:23.109Z",
|
||||
"author": "assistant"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"debugging",
|
||||
"research",
|
||||
"infrastructure",
|
||||
"root-cause",
|
||||
"Web Projects"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "8",
|
||||
"title": "Fix Kanban board - dynamic sync without hard refresh",
|
||||
"description": "Current board uses localStorage persistence which requires hard refresh (Cmd+Shift+R) to see task updates from code changes.",
|
||||
"type": "task",
|
||||
"status": "done",
|
||||
"priority": "medium",
|
||||
"projectId": "2",
|
||||
"sprintId": "sprint-1",
|
||||
"createdAt": "2026-02-18T17:01:23.109Z",
|
||||
"updatedAt": "2026-02-18T17:01:23.109Z",
|
||||
"comments": [
|
||||
{
|
||||
"id": "c44",
|
||||
"text": "COMPLETED: Added /api/tasks endpoint with file-based storage",
|
||||
"createdAt": "2026-02-18T17:01:23.109Z",
|
||||
"author": "assistant"
|
||||
},
|
||||
{
|
||||
"id": "c45",
|
||||
"text": "COMPLETED: Store now syncs from server on load and auto-syncs changes",
|
||||
"createdAt": "2026-02-18T17:01:23.109Z",
|
||||
"author": "assistant"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"ui",
|
||||
"sync",
|
||||
"localstorage",
|
||||
"real-time",
|
||||
"Web Projects"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "9",
|
||||
"title": "Add ability to edit task priority in Kanban board",
|
||||
"description": "Currently users cannot change task priority (Low/Medium/High/Urgent) from the UI.",
|
||||
"type": "task",
|
||||
"status": "done",
|
||||
"priority": "high",
|
||||
"projectId": "2",
|
||||
"sprintId": "sprint-1",
|
||||
"createdAt": "2026-02-18T17:01:23.109Z",
|
||||
"updatedAt": "2026-02-18T17:01:23.109Z",
|
||||
"comments": [
|
||||
{
|
||||
"id": "c28",
|
||||
"text": "COMPLETED: Added priority buttons to task detail dialog. Click any task to see Low/Medium/High/Urgent buttons with color coding. Changes apply immediately.",
|
||||
"createdAt": "2026-02-18T17:01:23.109Z",
|
||||
"author": "assistant"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"ui",
|
||||
"kanban",
|
||||
"feature",
|
||||
"priority",
|
||||
"Web Projects"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "10",
|
||||
"title": "RESEARCH: Find viable screenshot solution for OpenClaw on macOS",
|
||||
"description": "INVESTIGATION NEEDED: Find a reliable, persistent way for OpenClaw AI to capture screenshots of local websites running on macOS.",
|
||||
"type": "research",
|
||||
"status": "done",
|
||||
"priority": "high",
|
||||
"projectId": "2",
|
||||
"sprintId": "sprint-1",
|
||||
"createdAt": "2026-02-18T17:01:23.109Z",
|
||||
"updatedAt": "2026-02-18T17:01:23.109Z",
|
||||
"comments": [
|
||||
{
|
||||
"id": "c39",
|
||||
"text": "SUCCESS: Playwright + Google Chrome works! Successfully captured screenshot of http://localhost:3005 using headless Chrome",
|
||||
"createdAt": "2026-02-18T17:01:23.109Z",
|
||||
"author": "assistant"
|
||||
},
|
||||
{
|
||||
"id": "c47",
|
||||
"text": "COMPLETED: Playwright installed globally. Screenshots now work anytime without setup.",
|
||||
"createdAt": "2026-02-18T17:01:23.109Z",
|
||||
"author": "assistant"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"research",
|
||||
"screenshot",
|
||||
"macos",
|
||||
"openclaw",
|
||||
"investigation",
|
||||
"Web Projects"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "11",
|
||||
"title": "RESEARCH: Find iOS side projects with MRR potential",
|
||||
"description": "Research and identify iOS app ideas that have strong Monthly Recurring Revenue (MRR) opportunities.",
|
||||
"type": "research",
|
||||
"status": "done",
|
||||
"priority": "low",
|
||||
"projectId": "3",
|
||||
"createdAt": "2026-02-18T17:01:23.109Z",
|
||||
"updatedAt": "2026-02-20T05:02:42.002Z",
|
||||
"comments": [
|
||||
{
|
||||
"id": "c52",
|
||||
"text": "COMPLETED: Full research report saved to memory/ios-mrr-opportunities.md",
|
||||
"createdAt": "2026-02-18T17:01:23.109Z",
|
||||
"author": "assistant"
|
||||
},
|
||||
{
|
||||
"id": "c53",
|
||||
"text": "TOP 10 IDEAS: (1) AI Translator Keyboard - $15K/mo potential, (2) Finance Widget Suite, (3) Focus Timer with Live Activities - RECOMMENDED, (4) AI Photo Enhancer, (5) Habit Tracker with Social, (6) Local Business Review Widget, (7) Audio Journal with Voice-to-Text, (8) Plant Care Tracker, (9) Sleep Sounds with HomeKit, (10) Family Password Manager",
|
||||
"createdAt": "2026-02-18T17:01:23.109Z",
|
||||
"author": "assistant"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"ios",
|
||||
"mrr",
|
||||
"research",
|
||||
"side-project",
|
||||
"entrepreneurship",
|
||||
"app-ideas"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "12",
|
||||
"title": "Add markdown rendering to Blog Backup",
|
||||
"description": "The blog backup page currently shows raw markdown text instead of rendered HTML.",
|
||||
"type": "task",
|
||||
"status": "done",
|
||||
"priority": "high",
|
||||
"projectId": "2",
|
||||
"sprintId": "sprint-1",
|
||||
"createdAt": "2026-02-18T17:01:23.109Z",
|
||||
"updatedAt": "2026-02-18T17:01:23.109Z",
|
||||
"comments": [
|
||||
{
|
||||
"id": "c58",
|
||||
"text": "COMPLETED: Installed react-markdown and remark-gfm",
|
||||
"createdAt": "2026-02-18T17:01:23.109Z",
|
||||
"author": "assistant"
|
||||
},
|
||||
{
|
||||
"id": "c61",
|
||||
"text": "COMPLETED: Links now open in new tab with blue styling and hover effects",
|
||||
"createdAt": "2026-02-18T17:01:23.109Z",
|
||||
"author": "assistant"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"blog",
|
||||
"ui",
|
||||
"markdown",
|
||||
"frontend",
|
||||
"Web Projects"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "13",
|
||||
"title": "Research TTS options for Daily Digest podcast",
|
||||
"description": "Research free text-to-speech (TTS) tools to convert the daily digest blog posts into an audio podcast format.",
|
||||
"type": "research",
|
||||
"status": "open",
|
||||
"priority": "medium",
|
||||
"projectId": "2",
|
||||
"sprintId": "sprint-1",
|
||||
"createdAt": "2026-02-19T17:01:23.109Z",
|
||||
"updatedAt": "2026-02-19T17:01:23.109Z",
|
||||
"comments": [
|
||||
{
|
||||
"id": "c62",
|
||||
"text": "Goal: Convert daily digest text to audio for dog walks",
|
||||
"createdAt": "2026-02-19T17:01:23.109Z",
|
||||
"author": "user"
|
||||
},
|
||||
{
|
||||
"id": "c63",
|
||||
"text": "Requirement: Free or very low cost solution",
|
||||
"createdAt": "2026-02-19T17:01:23.109Z",
|
||||
"author": "user"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"research",
|
||||
"tts",
|
||||
"podcast",
|
||||
"audio",
|
||||
"digest",
|
||||
"accessibility",
|
||||
"Web Projects"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "14",
|
||||
"title": "Implement daily data backup system",
|
||||
"description": "Create automated daily backup of all web app data to Git. All 3 apps (gantt-board, blog-backup, heartbeat-monitor) now have data persistence with JSON files in data/ directories. Need daily backup cron job that commits data to Gitea to prevent data loss on server restarts.",
|
||||
"type": "task",
|
||||
"status": "done",
|
||||
"priority": "high",
|
||||
"projectId": "2",
|
||||
"sprintId": "sprint-1",
|
||||
"createdAt": "2026-02-19T13:00:00.000Z",
|
||||
"updatedAt": "2026-02-19T13:00:00.000Z",
|
||||
"comments": [
|
||||
{
|
||||
"id": "c66",
|
||||
"text": "All 3 apps verified to have data/ directories with JSON persistence",
|
||||
"createdAt": "2026-02-19T13:00:00.000Z",
|
||||
"author": "assistant"
|
||||
},
|
||||
{
|
||||
"id": "c67",
|
||||
"text": "Created daily-backup.sh script to commit data to Git",
|
||||
"createdAt": "2026-02-19T13:00:00.000Z",
|
||||
"author": "assistant"
|
||||
},
|
||||
{
|
||||
"id": "c68",
|
||||
"text": "Added cron job for 11:00 PM CST daily backups",
|
||||
"createdAt": "2026-02-19T13:00:00.000Z",
|
||||
"author": "assistant"
|
||||
},
|
||||
{
|
||||
"id": "c69",
|
||||
"text": "Backup logs to memory/backup.log for monitoring",
|
||||
"createdAt": "2026-02-19T13:00:00.000Z",
|
||||
"author": "assistant"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"backup",
|
||||
"infrastructure",
|
||||
"data-persistence",
|
||||
"automation",
|
||||
"Web Projects"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "15",
|
||||
"priority": "urgent",
|
||||
"title": "Add Sprint functionality to Gantt Board",
|
||||
"projectId": "2",
|
||||
"sprintId": "sprint-1",
|
||||
"updatedAt": "2026-02-20T05:24:24.353Z",
|
||||
"tags": [
|
||||
"Web Projects"
|
||||
],
|
||||
"status": "todo"
|
||||
}
|
||||
],
|
||||
"lastUpdated": 1771565064468,
|
||||
"sprints": [
|
||||
{
|
||||
"name": "Sprint 1",
|
||||
"goal": "Foundation and core features",
|
||||
"startDate": "2026-02-16",
|
||||
"endDate": "2026-02-22",
|
||||
"status": "active",
|
||||
"projectId": "2",
|
||||
"sprintId": "sprint-1",
|
||||
"id": "sprint-1",
|
||||
"createdAt": "2026-02-16T00:00:00.000Z"
|
||||
},
|
||||
{
|
||||
"name": "Sprint 2",
|
||||
"goal": "",
|
||||
"startDate": "2026-02-23",
|
||||
"endDate": "2026-03-01",
|
||||
"status": "planning",
|
||||
"projectId": "1",
|
||||
"id": "1771551323429",
|
||||
"createdAt": "2026-02-20T01:35:23.429Z"
|
||||
},
|
||||
{
|
||||
"name": "Sprint 3",
|
||||
"goal": "",
|
||||
"startDate": "2026-03-02",
|
||||
"endDate": "2026-03-08",
|
||||
"status": "planning",
|
||||
"projectId": "1",
|
||||
"id": "1771551465241",
|
||||
"createdAt": "2026-02-20T01:37:45.241Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
428
package-lock.json
generated
428
package-lock.json
generated
@ -16,11 +16,13 @@
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||
"@radix-ui/react-label": "^2.1.8",
|
||||
"@radix-ui/react-select": "^2.2.6",
|
||||
"better-sqlite3": "^12.6.2",
|
||||
"firebase": "^12.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@types/better-sqlite3": "^7.6.13",
|
||||
"@types/frappe-gantt": "^0.9.0",
|
||||
"@types/node": "^20.19.33",
|
||||
"@types/react": "^19.2.14",
|
||||
@ -3663,6 +3665,16 @@
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/better-sqlite3": {
|
||||
"version": "7.6.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz",
|
||||
"integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-array": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz",
|
||||
@ -4700,6 +4712,26 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/baseline-browser-mapping": {
|
||||
"version": "2.9.19",
|
||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz",
|
||||
@ -4710,6 +4742,40 @@
|
||||
"baseline-browser-mapping": "dist/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/better-sqlite3": {
|
||||
"version": "12.6.2",
|
||||
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.6.2.tgz",
|
||||
"integrity": "sha512-8VYKM3MjCa9WcaSAI3hzwhmyHVlH8tiGFwf0RlTsZPWJ1I5MkzjiudCo4KC4DxOaL/53A5B1sI/IbldNFDbsKA==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bindings": "^1.5.0",
|
||||
"prebuild-install": "^7.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "20.x || 22.x || 23.x || 24.x || 25.x"
|
||||
}
|
||||
},
|
||||
"node_modules/bindings": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
|
||||
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"file-uri-to-path": "1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bl": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
|
||||
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"buffer": "^5.5.0",
|
||||
"inherits": "^2.0.4",
|
||||
"readable-stream": "^3.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
@ -4768,6 +4834,30 @@
|
||||
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
|
||||
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"base64-js": "^1.3.1",
|
||||
"ieee754": "^1.1.13"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
|
||||
@ -4866,6 +4956,12 @@
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/chownr": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
|
||||
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/class-variance-authority": {
|
||||
"version": "0.7.1",
|
||||
"resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
|
||||
@ -5193,6 +5289,30 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/decompress-response": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
|
||||
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mimic-response": "^3.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/deep-extend": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
|
||||
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/deep-is": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
|
||||
@ -5240,7 +5360,6 @@
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
@ -5294,6 +5413,15 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/end-of-stream": {
|
||||
"version": "1.4.5",
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
|
||||
"integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"once": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/enhanced-resolve": {
|
||||
"version": "5.19.0",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz",
|
||||
@ -5997,6 +6125,15 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/expand-template": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
|
||||
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
|
||||
"license": "(MIT OR WTFPL)",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
@ -6083,6 +6220,12 @@
|
||||
"node": ">=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/file-uri-to-path": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
@ -6235,6 +6378,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fs-constants": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
|
||||
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
@ -6399,6 +6548,12 @@
|
||||
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/github-from-package": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
|
||||
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/glob-parent": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
||||
@ -6585,6 +6740,26 @@
|
||||
"integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/ignore": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
||||
@ -6633,6 +6808,18 @@
|
||||
"node": ">=0.8.19"
|
||||
}
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/ini": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
|
||||
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/internal-slot": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz",
|
||||
@ -7624,6 +7811,18 @@
|
||||
"node": ">=8.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mimic-response": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
|
||||
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
@ -7641,12 +7840,17 @@
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/mkdirp-classic": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
|
||||
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/motion-dom": {
|
||||
"version": "12.34.1",
|
||||
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.34.1.tgz",
|
||||
@ -7690,6 +7894,12 @@
|
||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/napi-build-utils": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
|
||||
"integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/napi-postinstall": {
|
||||
"version": "0.3.4",
|
||||
"resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz",
|
||||
@ -7795,6 +8005,30 @@
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/node-abi": {
|
||||
"version": "3.87.0",
|
||||
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.87.0.tgz",
|
||||
"integrity": "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"semver": "^7.3.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/node-abi/node_modules/semver": {
|
||||
"version": "7.7.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
||||
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/node-exports-info": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz",
|
||||
@ -7944,6 +8178,15 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/optionator": {
|
||||
"version": "0.9.4",
|
||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
||||
@ -8118,6 +8361,33 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/prebuild-install": {
|
||||
"version": "7.1.3",
|
||||
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
|
||||
"integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==",
|
||||
"deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"detect-libc": "^2.0.0",
|
||||
"expand-template": "^2.0.3",
|
||||
"github-from-package": "0.0.0",
|
||||
"minimist": "^1.2.3",
|
||||
"mkdirp-classic": "^0.5.3",
|
||||
"napi-build-utils": "^2.0.0",
|
||||
"node-abi": "^3.3.0",
|
||||
"pump": "^3.0.0",
|
||||
"rc": "^1.2.7",
|
||||
"simple-get": "^4.0.0",
|
||||
"tar-fs": "^2.0.0",
|
||||
"tunnel-agent": "^0.6.0"
|
||||
},
|
||||
"bin": {
|
||||
"prebuild-install": "bin.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/prelude-ls": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
||||
@ -8171,6 +8441,16 @@
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pump": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz",
|
||||
"integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"end-of-stream": "^1.1.0",
|
||||
"once": "^1.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
@ -8202,6 +8482,30 @@
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/rc": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
|
||||
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
|
||||
"license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
|
||||
"dependencies": {
|
||||
"deep-extend": "^0.6.0",
|
||||
"ini": "~1.3.0",
|
||||
"minimist": "^1.2.0",
|
||||
"strip-json-comments": "~2.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"rc": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/rc/node_modules/strip-json-comments": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
|
||||
"integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "19.2.4",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
|
||||
@ -8324,6 +8628,20 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/recharts": {
|
||||
"version": "3.7.0",
|
||||
"resolved": "https://registry.npmjs.org/recharts/-/recharts-3.7.0.tgz",
|
||||
@ -8807,6 +9125,51 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-concat": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
|
||||
"integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/simple-get": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
|
||||
"integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"decompress-response": "^6.0.0",
|
||||
"once": "^1.3.1",
|
||||
"simple-concat": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
@ -8838,6 +9201,15 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
@ -9088,6 +9460,34 @@
|
||||
"url": "https://opencollective.com/webpack"
|
||||
}
|
||||
},
|
||||
"node_modules/tar-fs": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz",
|
||||
"integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chownr": "^1.1.1",
|
||||
"mkdirp-classic": "^0.5.2",
|
||||
"pump": "^3.0.0",
|
||||
"tar-stream": "^2.1.4"
|
||||
}
|
||||
},
|
||||
"node_modules/tar-stream": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
|
||||
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bl": "^4.0.3",
|
||||
"end-of-stream": "^1.4.1",
|
||||
"fs-constants": "^1.0.0",
|
||||
"inherits": "^2.0.3",
|
||||
"readable-stream": "^3.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/tiny-invariant": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
|
||||
@ -9221,6 +9621,18 @@
|
||||
"fsevents": "~2.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/tunnel-agent": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||
"integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/type-check": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
||||
@ -9504,6 +9916,12 @@
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/victory-vendor": {
|
||||
"version": "37.3.6",
|
||||
"resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz",
|
||||
@ -9688,6 +10106,12 @@
|
||||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/y18n": {
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||
|
||||
@ -16,11 +16,13 @@
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||
"@radix-ui/react-label": "^2.1.8",
|
||||
"@radix-ui/react-select": "^2.2.6",
|
||||
"better-sqlite3": "^12.6.2",
|
||||
"firebase": "^12.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@types/better-sqlite3": "^7.6.13",
|
||||
"@types/frappe-gantt": "^0.9.0",
|
||||
"@types/node": "^20.19.33",
|
||||
"@types/react": "^19.2.14",
|
||||
|
||||
@ -1,129 +1,36 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
||||
import { join } from "path";
|
||||
import { getData, saveData, type DataStore, type Task } from "@/lib/server/taskDb";
|
||||
|
||||
const DATA_FILE = join(process.cwd(), "data", "tasks.json");
|
||||
console.log('>>> API ROUTE: module loaded, process.cwd():', process.cwd());
|
||||
console.log('>>> API ROUTE: DATA_FILE:', DATA_FILE);
|
||||
|
||||
interface Task {
|
||||
id: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
type: 'idea' | 'task' | 'bug' | 'research' | 'plan';
|
||||
status: 'open' | 'todo' | 'blocked' | 'in-progress' | 'review' | 'validate' | 'archived' | 'canceled' | 'done';
|
||||
priority: 'low' | 'medium' | 'high' | 'urgent';
|
||||
projectId: string;
|
||||
sprintId?: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
dueDate?: string;
|
||||
comments: { id: string; text: string; createdAt: string; author: 'user' | 'assistant' }[];
|
||||
tags: string[];
|
||||
}
|
||||
|
||||
interface Project {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
color: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
interface Sprint {
|
||||
id: string;
|
||||
name: string;
|
||||
goal?: string;
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
status: 'planning' | 'active' | 'completed';
|
||||
projectId: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
interface DataStore {
|
||||
projects: Project[];
|
||||
tasks: Task[];
|
||||
sprints: Sprint[];
|
||||
lastUpdated: number;
|
||||
}
|
||||
|
||||
const defaultData: DataStore = {
|
||||
projects: [
|
||||
{ id: '1', name: 'OpenClaw iOS', description: 'Main iOS app development', color: '#8b5cf6', createdAt: new Date().toISOString() },
|
||||
{ id: '2', name: 'Web Projects', description: 'Web tools and dashboards', color: '#3b82f6', createdAt: new Date().toISOString() },
|
||||
{ id: '3', name: 'Research', description: 'Experiments and learning', color: '#10b981', createdAt: new Date().toISOString() },
|
||||
],
|
||||
tasks: [],
|
||||
sprints: [],
|
||||
lastUpdated: Date.now(),
|
||||
};
|
||||
|
||||
function getData(): DataStore {
|
||||
console.log('>>> getData: checking file:', DATA_FILE);
|
||||
console.log('>>> getData: exists?', existsSync(DATA_FILE));
|
||||
if (!existsSync(DATA_FILE)) {
|
||||
console.log('>>> getData: file not found, returning defaultData');
|
||||
return defaultData;
|
||||
}
|
||||
try {
|
||||
const rawData = readFileSync(DATA_FILE, "utf-8");
|
||||
console.log('>>> getData: read file, length:', rawData.length);
|
||||
const data = JSON.parse(rawData);
|
||||
console.log('>>> getData: parsed data has', data.tasks?.length, 'tasks');
|
||||
return data;
|
||||
} catch (err) {
|
||||
console.error('>>> getData: error reading/parsing file:', err);
|
||||
return defaultData;
|
||||
}
|
||||
}
|
||||
|
||||
function saveData(data: DataStore) {
|
||||
const dir = join(process.cwd(), "data");
|
||||
if (!existsSync(dir)) {
|
||||
mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
data.lastUpdated = Date.now();
|
||||
writeFileSync(DATA_FILE, JSON.stringify(data, null, 2));
|
||||
}
|
||||
export const runtime = "nodejs";
|
||||
|
||||
// GET - fetch all tasks, projects, and sprints
|
||||
export async function GET() {
|
||||
console.log('>>> API GET: fetching data');
|
||||
console.log('>>> API GET: DATA_FILE path:', DATA_FILE);
|
||||
try {
|
||||
const data = getData();
|
||||
console.log('>>> API GET: returning data with', data.tasks?.length, 'tasks,', data.projects?.length, 'projects,', data.sprints?.length, 'sprints');
|
||||
console.log('>>> API GET: lastUpdated:', data.lastUpdated);
|
||||
return NextResponse.json(data);
|
||||
} catch (error) {
|
||||
console.error(">>> API GET: database error:", error);
|
||||
return NextResponse.json({ error: "Failed to fetch data" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
// POST - create or update tasks, projects, or sprints
|
||||
export async function POST(request: Request) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
console.log('>>> API POST: received body keys:', Object.keys(body));
|
||||
console.log('>>> API POST: has task?', !!body.task, '| has tasks?', !!body.tasks, '| has projects?', !!body.projects, '| has sprints?', !!body.sprints);
|
||||
console.log('>>> API POST: tasks array length:', body.tasks?.length);
|
||||
const { task, tasks, projects, sprints } = body as {
|
||||
task?: Task;
|
||||
tasks?: Task[];
|
||||
projects?: DataStore["projects"];
|
||||
sprints?: DataStore["sprints"];
|
||||
};
|
||||
|
||||
const { task, tasks, projects, sprints } = body;
|
||||
const data = getData();
|
||||
console.log('>>> API POST: current data in file - tasks:', data.tasks?.length, 'projects:', data.projects?.length, 'sprints:', data.sprints?.length);
|
||||
|
||||
// Update projects if provided
|
||||
if (projects) {
|
||||
console.log('>>> API POST: updating projects:', projects.length);
|
||||
data.projects = projects;
|
||||
}
|
||||
if (projects) data.projects = projects;
|
||||
if (sprints) data.sprints = sprints;
|
||||
|
||||
// Update sprints if provided
|
||||
if (sprints) {
|
||||
console.log('>>> API POST: updating sprints:', sprints.length);
|
||||
data.sprints = sprints;
|
||||
}
|
||||
|
||||
// Update or add single task
|
||||
if (task) {
|
||||
console.log('>>> API POST: updating single task:', task.id);
|
||||
const existingIndex = data.tasks.findIndex((t) => t.id === task.id);
|
||||
if (existingIndex >= 0) {
|
||||
data.tasks[existingIndex] = { ...task, updatedAt: new Date().toISOString() };
|
||||
@ -137,17 +44,14 @@ export async function POST(request: Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// Update all tasks if tasks array provided
|
||||
if (tasks && Array.isArray(tasks)) {
|
||||
console.log('>>> API POST: updating ALL tasks array:', tasks.length);
|
||||
data.tasks = tasks;
|
||||
}
|
||||
|
||||
saveData(data);
|
||||
console.log('>>> API POST: saved! New task count:', data.tasks.length);
|
||||
return NextResponse.json({ success: true, data });
|
||||
const saved = saveData(data);
|
||||
return NextResponse.json({ success: true, data: saved });
|
||||
} catch (error) {
|
||||
console.error('>>> API POST: error:', error);
|
||||
console.error(">>> API POST: database error:", error);
|
||||
return NextResponse.json({ error: "Failed to save" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@ -155,12 +59,14 @@ export async function POST(request: Request) {
|
||||
// DELETE - remove a task
|
||||
export async function DELETE(request: Request) {
|
||||
try {
|
||||
const { id } = await request.json();
|
||||
const { id } = (await request.json()) as { id: string };
|
||||
const data = getData();
|
||||
data.tasks = data.tasks.filter((t) => t.id !== id);
|
||||
saveData(data);
|
||||
return NextResponse.json({ success: true });
|
||||
} catch {
|
||||
} catch (error) {
|
||||
console.error(">>> API DELETE: database error:", error);
|
||||
return NextResponse.json({ error: "Failed to delete" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -185,13 +185,12 @@ function KanbanTaskCard({
|
||||
onOpen: () => void
|
||||
onDelete: () => void
|
||||
}) {
|
||||
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useDraggable({
|
||||
const { attributes, listeners, setNodeRef, transform, isDragging } = useDraggable({
|
||||
id: task.id,
|
||||
})
|
||||
|
||||
const style = {
|
||||
transform: CSS.Translate.toString(transform),
|
||||
transition,
|
||||
opacity: isDragging ? 0.6 : 1,
|
||||
}
|
||||
|
||||
|
||||
323
src/lib/server/taskDb.ts
Normal file
323
src/lib/server/taskDb.ts
Normal file
@ -0,0 +1,323 @@
|
||||
import Database from "better-sqlite3";
|
||||
import { mkdirSync } from "fs";
|
||||
import { join } from "path";
|
||||
|
||||
export interface Task {
|
||||
id: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
type: "idea" | "task" | "bug" | "research" | "plan";
|
||||
status: "open" | "todo" | "blocked" | "in-progress" | "review" | "validate" | "archived" | "canceled" | "done";
|
||||
priority: "low" | "medium" | "high" | "urgent";
|
||||
projectId: string;
|
||||
sprintId?: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
dueDate?: string;
|
||||
comments: { id: string; text: string; createdAt: string; author: "user" | "assistant" }[];
|
||||
tags: string[];
|
||||
}
|
||||
|
||||
export interface Project {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
color: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface Sprint {
|
||||
id: string;
|
||||
name: string;
|
||||
goal?: string;
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
status: "planning" | "active" | "completed";
|
||||
projectId: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface DataStore {
|
||||
projects: Project[];
|
||||
tasks: Task[];
|
||||
sprints: Sprint[];
|
||||
lastUpdated: number;
|
||||
}
|
||||
|
||||
const DATA_DIR = join(process.cwd(), "data");
|
||||
const DB_FILE = join(DATA_DIR, "tasks.db");
|
||||
|
||||
const defaultData: DataStore = {
|
||||
projects: [
|
||||
{ id: "1", name: "OpenClaw iOS", description: "Main iOS app development", color: "#8b5cf6", createdAt: new Date().toISOString() },
|
||||
{ id: "2", name: "Web Projects", description: "Web tools and dashboards", color: "#3b82f6", createdAt: new Date().toISOString() },
|
||||
{ id: "3", name: "Research", description: "Experiments and learning", color: "#10b981", createdAt: new Date().toISOString() },
|
||||
],
|
||||
tasks: [],
|
||||
sprints: [],
|
||||
lastUpdated: Date.now(),
|
||||
};
|
||||
|
||||
type SqliteDb = InstanceType<typeof Database>;
|
||||
|
||||
let db: SqliteDb | null = null;
|
||||
|
||||
function safeParseArray<T>(value: string | null, fallback: T[]): T[] {
|
||||
if (!value) return fallback;
|
||||
try {
|
||||
const parsed = JSON.parse(value);
|
||||
return Array.isArray(parsed) ? (parsed as T[]) : fallback;
|
||||
} catch {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeTask(task: Partial<Task>): Task {
|
||||
return {
|
||||
id: String(task.id ?? Date.now()),
|
||||
title: String(task.title ?? ""),
|
||||
description: task.description || undefined,
|
||||
type: (task.type as Task["type"]) ?? "task",
|
||||
status: (task.status as Task["status"]) ?? "open",
|
||||
priority: (task.priority as Task["priority"]) ?? "medium",
|
||||
projectId: String(task.projectId ?? "2"),
|
||||
sprintId: task.sprintId || undefined,
|
||||
createdAt: task.createdAt || new Date().toISOString(),
|
||||
updatedAt: task.updatedAt || new Date().toISOString(),
|
||||
dueDate: task.dueDate || undefined,
|
||||
comments: Array.isArray(task.comments) ? task.comments : [],
|
||||
tags: Array.isArray(task.tags) ? task.tags.filter((tag): tag is string => typeof tag === "string") : [],
|
||||
};
|
||||
}
|
||||
|
||||
function setLastUpdated(database: SqliteDb, value: number) {
|
||||
database
|
||||
.prepare(`
|
||||
INSERT INTO meta (key, value)
|
||||
VALUES ('lastUpdated', ?)
|
||||
ON CONFLICT(key) DO UPDATE SET value = excluded.value
|
||||
`)
|
||||
.run(String(value));
|
||||
}
|
||||
|
||||
function getLastUpdated(database: SqliteDb): number {
|
||||
const row = database.prepare("SELECT value FROM meta WHERE key = 'lastUpdated'").get() as { value?: string } | undefined;
|
||||
const parsed = Number(row?.value ?? Date.now());
|
||||
return Number.isFinite(parsed) ? parsed : Date.now();
|
||||
}
|
||||
|
||||
function replaceAllData(database: SqliteDb, data: DataStore) {
|
||||
const write = database.transaction((payload: DataStore) => {
|
||||
database.exec("DELETE FROM projects;");
|
||||
database.exec("DELETE FROM sprints;");
|
||||
database.exec("DELETE FROM tasks;");
|
||||
|
||||
const insertProject = database.prepare(`
|
||||
INSERT INTO projects (id, name, description, color, createdAt)
|
||||
VALUES (@id, @name, @description, @color, @createdAt)
|
||||
`);
|
||||
const insertSprint = database.prepare(`
|
||||
INSERT INTO sprints (id, name, goal, startDate, endDate, status, projectId, createdAt)
|
||||
VALUES (@id, @name, @goal, @startDate, @endDate, @status, @projectId, @createdAt)
|
||||
`);
|
||||
const insertTask = database.prepare(`
|
||||
INSERT INTO tasks (id, title, description, type, status, priority, projectId, sprintId, createdAt, updatedAt, dueDate, comments, tags)
|
||||
VALUES (@id, @title, @description, @type, @status, @priority, @projectId, @sprintId, @createdAt, @updatedAt, @dueDate, @comments, @tags)
|
||||
`);
|
||||
|
||||
for (const project of payload.projects) {
|
||||
insertProject.run({
|
||||
id: project.id,
|
||||
name: project.name,
|
||||
description: project.description ?? null,
|
||||
color: project.color,
|
||||
createdAt: project.createdAt,
|
||||
});
|
||||
}
|
||||
|
||||
for (const sprint of payload.sprints) {
|
||||
insertSprint.run({
|
||||
id: sprint.id,
|
||||
name: sprint.name,
|
||||
goal: sprint.goal ?? null,
|
||||
startDate: sprint.startDate,
|
||||
endDate: sprint.endDate,
|
||||
status: sprint.status,
|
||||
projectId: sprint.projectId,
|
||||
createdAt: sprint.createdAt,
|
||||
});
|
||||
}
|
||||
|
||||
for (const task of payload.tasks.map(normalizeTask)) {
|
||||
insertTask.run({
|
||||
...task,
|
||||
sprintId: task.sprintId ?? null,
|
||||
dueDate: task.dueDate ?? null,
|
||||
comments: JSON.stringify(task.comments ?? []),
|
||||
tags: JSON.stringify(task.tags ?? []),
|
||||
});
|
||||
}
|
||||
|
||||
setLastUpdated(database, payload.lastUpdated || Date.now());
|
||||
});
|
||||
|
||||
write(data);
|
||||
}
|
||||
|
||||
function seedIfEmpty(database: SqliteDb) {
|
||||
const counts = database
|
||||
.prepare(
|
||||
`
|
||||
SELECT
|
||||
(SELECT COUNT(*) FROM projects) AS projectsCount,
|
||||
(SELECT COUNT(*) FROM sprints) AS sprintsCount,
|
||||
(SELECT COUNT(*) FROM tasks) AS tasksCount
|
||||
`
|
||||
)
|
||||
.get() as { projectsCount: number; sprintsCount: number; tasksCount: number };
|
||||
|
||||
if (counts.projectsCount > 0 || counts.sprintsCount > 0 || counts.tasksCount > 0) return;
|
||||
replaceAllData(database, defaultData);
|
||||
}
|
||||
|
||||
function getDb(): SqliteDb {
|
||||
if (db) return db;
|
||||
|
||||
mkdirSync(DATA_DIR, { recursive: true });
|
||||
const database = new Database(DB_FILE);
|
||||
database.pragma("journal_mode = WAL");
|
||||
database.exec(`
|
||||
CREATE TABLE IF NOT EXISTS projects (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
color TEXT NOT NULL,
|
||||
createdAt TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS sprints (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
goal TEXT,
|
||||
startDate TEXT NOT NULL,
|
||||
endDate TEXT NOT NULL,
|
||||
status TEXT NOT NULL,
|
||||
projectId TEXT NOT NULL,
|
||||
createdAt TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tasks (
|
||||
id TEXT PRIMARY KEY,
|
||||
title TEXT NOT NULL,
|
||||
description TEXT,
|
||||
type TEXT NOT NULL,
|
||||
status TEXT NOT NULL,
|
||||
priority TEXT NOT NULL,
|
||||
projectId TEXT NOT NULL,
|
||||
sprintId TEXT,
|
||||
createdAt TEXT NOT NULL,
|
||||
updatedAt TEXT NOT NULL,
|
||||
dueDate TEXT,
|
||||
comments TEXT NOT NULL DEFAULT '[]',
|
||||
tags TEXT NOT NULL DEFAULT '[]'
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS meta (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL
|
||||
);
|
||||
`);
|
||||
|
||||
seedIfEmpty(database);
|
||||
db = database;
|
||||
return database;
|
||||
}
|
||||
|
||||
export function getData(): DataStore {
|
||||
const database = getDb();
|
||||
|
||||
const projects = database.prepare("SELECT * FROM projects ORDER BY createdAt ASC").all() as Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
color: string;
|
||||
createdAt: string;
|
||||
}>;
|
||||
|
||||
const sprints = database.prepare("SELECT * FROM sprints ORDER BY startDate ASC").all() as Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
goal: string | null;
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
status: Sprint["status"];
|
||||
projectId: string;
|
||||
createdAt: string;
|
||||
}>;
|
||||
|
||||
const tasks = database.prepare("SELECT * FROM tasks ORDER BY createdAt ASC").all() as Array<{
|
||||
id: string;
|
||||
title: string;
|
||||
description: string | null;
|
||||
type: Task["type"];
|
||||
status: Task["status"];
|
||||
priority: Task["priority"];
|
||||
projectId: string;
|
||||
sprintId: string | null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
dueDate: string | null;
|
||||
comments: string | null;
|
||||
tags: string | null;
|
||||
}>;
|
||||
|
||||
return {
|
||||
projects: projects.map((project) => ({
|
||||
id: project.id,
|
||||
name: project.name,
|
||||
description: project.description ?? undefined,
|
||||
color: project.color,
|
||||
createdAt: project.createdAt,
|
||||
})),
|
||||
sprints: sprints.map((sprint) => ({
|
||||
id: sprint.id,
|
||||
name: sprint.name,
|
||||
goal: sprint.goal ?? undefined,
|
||||
startDate: sprint.startDate,
|
||||
endDate: sprint.endDate,
|
||||
status: sprint.status,
|
||||
projectId: sprint.projectId,
|
||||
createdAt: sprint.createdAt,
|
||||
})),
|
||||
tasks: tasks.map((task) => ({
|
||||
id: task.id,
|
||||
title: task.title,
|
||||
description: task.description ?? undefined,
|
||||
type: task.type,
|
||||
status: task.status,
|
||||
priority: task.priority,
|
||||
projectId: task.projectId,
|
||||
sprintId: task.sprintId ?? undefined,
|
||||
createdAt: task.createdAt,
|
||||
updatedAt: task.updatedAt,
|
||||
dueDate: task.dueDate ?? undefined,
|
||||
comments: safeParseArray(task.comments, []),
|
||||
tags: safeParseArray(task.tags, []),
|
||||
})),
|
||||
lastUpdated: getLastUpdated(database),
|
||||
};
|
||||
}
|
||||
|
||||
export function saveData(data: DataStore): DataStore {
|
||||
const database = getDb();
|
||||
const payload: DataStore = {
|
||||
...data,
|
||||
projects: data.projects ?? [],
|
||||
sprints: data.sprints ?? [],
|
||||
tasks: (data.tasks ?? []).map(normalizeTask),
|
||||
lastUpdated: Date.now(),
|
||||
};
|
||||
|
||||
replaceAllData(database, payload);
|
||||
return getData();
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user