{"id":51485103,"url":"https://github.com/es94111/assetpilot","last_synced_at":"2026-07-07T06:00:20.699Z","repository":{"id":345906617,"uuid":"1186920334","full_name":"es94111/AssetPilot","owner":"es94111","description":"個人資產管理網頁應用程式，整合記帳、預算、股票紀錄與報表分析。支援 TWSE 即時股價同步、多帳戶匯率自動轉換、ChaCha20 資料庫加密與 Google SSO 登入。","archived":false,"fork":false,"pushed_at":"2026-07-02T10:58:04.000Z","size":170978,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-02T12:28:30.029Z","etag":null,"topics":["bookkeeping","budget","chartjs","express","finance","google-oauth","jwt","nodejs","personal-finance","self-hosted","sqlite","stock-portfolio","twse"],"latest_commit_sha":null,"homepage":"","language":"Dart","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/es94111.png","metadata":{"files":{"readme":"README.md","changelog":"changelog.json","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-20T06:10:32.000Z","updated_at":"2026-07-02T10:58:06.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/es94111/AssetPilot","commit_stats":null,"previous_names":["es94111/assetpilot"],"tags_count":115,"template":false,"template_full_name":null,"purl":"pkg:github/es94111/AssetPilot","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/es94111%2FAssetPilot","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/es94111%2FAssetPilot/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/es94111%2FAssetPilot/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/es94111%2FAssetPilot/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/es94111","download_url":"https://codeload.github.com/es94111/AssetPilot/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/es94111%2FAssetPilot/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35216572,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-07T02:00:07.222Z","response_time":90,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bookkeeping","budget","chartjs","express","finance","google-oauth","jwt","nodejs","personal-finance","self-hosted","sqlite","stock-portfolio","twse"],"created_at":"2026-07-07T06:00:19.693Z","updated_at":"2026-07-07T06:00:20.692Z","avatar_url":"https://github.com/es94111.png","language":"Dart","funding_links":[],"categories":[],"sub_categories":[],"readme":"# AssetPilot — 資產管理\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"logo.svg\" alt=\"AssetPilot Logo\" width=\"120\"\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  自托管、資料加密的個人資產管理平台（Web + Android App）— 記帳、預算、股票紀錄、報表、稽核日誌，一站搞定。\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/version-4.88.2-blue\" alt=\"version\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/node-%3E%3D24-brightgreen\" alt=\"node\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/next.js-16.x-000000\" alt=\"next.js\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/openapi-3.2.0-6BA539\" alt=\"openapi\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/license-AGPL--3.0-blue\" alt=\"license\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/docker-ready-2496ED\" alt=\"docker\"\u003e\n\u003c/p\u003e\n\n---\n\n## 目錄\n\n- [螢幕截圖](#螢幕截圖)\n- [特色](#特色)\n- [功能總覽](#功能總覽)\n- [技術架構](#技術架構)\n- [快速啟動](#快速啟動)\n- [環境變數](#環境變數)\n- [反向代理](#反向代理)\n- [認證機制](#認證機制)\n- [使用指南](#使用指南)\n- [資料治理](#資料治理)\n- [安全性](#安全性)\n- [外部 API 來源](#外部-api-來源)\n- [專案治理](#專案治理)\n- [授權](#授權)\n\n---\n\n## 螢幕截圖\n\n\u003e 以下為行動版 App（Android）實際畫面；網頁版截圖整理中。\n\n\u003ctable\u003e\n  \u003ctr\u003e\n    \u003ctd align=\"center\" width=\"25%\"\u003e\u003cimg src=\"docs/screenshots/app/02-dashboard.png\" width=\"200\" alt=\"儀表板\"\u003e\u003cbr\u003e\u003csub\u003e\u003cb\u003e儀表板\u003c/b\u003e\u003cbr\u003e月份切換、收支淨額、支出分類圓餅\u003c/sub\u003e\u003c/td\u003e\n    \u003ctd align=\"center\" width=\"25%\"\u003e\u003cimg src=\"docs/screenshots/app/03-transactions.png\" width=\"200\" alt=\"記帳\"\u003e\u003cbr\u003e\u003csub\u003e\u003cb\u003e記帳\u003c/b\u003e\u003cbr\u003e收支列表、篩選、滑動刪除\u003c/sub\u003e\u003c/td\u003e\n    \u003ctd align=\"center\" width=\"25%\"\u003e\u003cimg src=\"docs/screenshots/app/04-stocks-holdings.png\" width=\"200\" alt=\"持股\"\u003e\u003cbr\u003e\u003csub\u003e\u003cb\u003e持股\u003c/b\u003e\u003cbr\u003e即時市值、未實現損益\u003c/sub\u003e\u003c/td\u003e\n    \u003ctd align=\"center\" width=\"25%\"\u003e\u003cimg src=\"docs/screenshots/app/05-stocks-realized.png\" width=\"200\" alt=\"已實現損益\"\u003e\u003cbr\u003e\u003csub\u003e\u003cb\u003e已實現損益\u003c/b\u003e\u003cbr\u003eFIFO 逐筆損益彙總\u003c/sub\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd align=\"center\" width=\"25%\"\u003e\u003cimg src=\"docs/screenshots/app/06-budgets.png\" width=\"200\" alt=\"預算\"\u003e\u003cbr\u003e\u003csub\u003e\u003cb\u003e預算\u003c/b\u003e\u003cbr\u003e月度與分類預算進度\u003c/sub\u003e\u003c/td\u003e\n    \u003ctd align=\"center\" width=\"25%\"\u003e\u003cimg src=\"docs/screenshots/app/07-reports.png\" width=\"200\" alt=\"統計報表\"\u003e\u003cbr\u003e\u003csub\u003e\u003cb\u003e統計報表\u003c/b\u003e\u003cbr\u003e分類分布與趨勢\u003c/sub\u003e\u003c/td\u003e\n    \u003ctd align=\"center\" width=\"25%\"\u003e\u003cimg src=\"docs/screenshots/app/08-more.png\" width=\"200\" alt=\"更多\"\u003e\u003cbr\u003e\u003csub\u003e\u003cb\u003e更多\u003c/b\u003e\u003cbr\u003e帳號安全、設定、報表通知\u003c/sub\u003e\u003c/td\u003e\n    \u003ctd align=\"center\" width=\"25%\"\u003e\u003cimg src=\"docs/screenshots/app/01-login.png\" width=\"200\" alt=\"登入\"\u003e\u003cbr\u003e\u003csub\u003e\u003cb\u003e登入\u003c/b\u003e\u003cbr\u003e帳密 / Google / LINE / Passkey\u003c/sub\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\n---\n\n## 特色\n\n| 特色 | 說明 |\n| ---- | ---- |\n| 🏠 自托管，資料自控 | 資料庫 ChaCha20-Poly1305 加密儲存於本地，財務資訊不上傳任何外部伺服器 |\n| 🐳 Docker 一鍵部署 | 單行指令啟動；JWT 與資料庫加密金鑰首次啟動自動產生並寫入持久化 volume |\n| 📊 台股深度整合 | 串接 TWSE OpenAPI：即時股價、除權息自動同步、FIFO 全精度逐筆損益 |\n| 💱 多幣別支援 | 串接 exchangerate-api.com 即時匯率，ISO 4217 白名單驗證 |\n| 🔐 多重認證 | 帳密 / Google SSO / LINE Login（Authorization Code Flow）/ Passkey（WebAuthn） |\n| 🛡️ 稽核可審計 | 資料匯出匯入、備份還原、登入嘗試、路由攔截皆可追溯；保留天數可調 |\n| 🧭 URL-first SPA | 任何頁面可直連、書籤、分享；F5 重整不掉頁；上一頁 / 下一頁完整還原 |\n| 🌗 三模式主題 | system / light / dark；跨裝置同步；登入頁無 FOUC 樂觀渲染 |\n| 📦 純伺服端 CSV | 匯出含 UTF-8 BOM + Formula Injection 防護；匯入採 DB transaction 原子化 |\n| 📜 OpenAPI 3.2.0 | 完整契約檔案，可掛載至 Cloudflare API Shield 啟用 schema 驗證 |\n| 📱 響應式設計 | 桌機側邊欄常駐、行動裝置漢堡選單；\u003c 768px 自動切換 |\n\n---\n\n## 功能總覽\n\n### 收支與預算\n\n- **儀表板**：月份切換、總資產 / 收入 / 支出 / 淨額卡片、雙圓餅（含「（其他）」群組）、最近交易\n- **交易管理**：CRUD、跨帳戶轉帳（雙向配對）、批次刪除 / 變更分類 / 變更日期、未來交易標記\n- **預算管理**：月度總預算 + 分類預算；進度條四段配色（綠 / 中性 / 黃接近上限 / 紅超支）；歷史與未來月份切換\n- **帳戶管理**：多幣別、銀行 / 信用卡 / 證券 / 現金，可排除特定帳戶於總資產統計\n- **分類管理**：父子兩層、自訂顏色、CSV 匯出匯入（先父後子）\n- **固定收支**：週期性收支自動產生交易，支援外幣；登入時 server-side 觸發（30 筆軟上限 + setImmediate 背景續跑）；並發冪等保護\n\n### 股票投資\n\n- **持股總覽**：即時市值、未實現損益、整體報酬率、三段策略抓股價（盤中即時 / 盤後收盤 / 備援）\n- **股價自動更新**：伺服器於台股交易時段（台北時間週一~五 09:00–14:00）內每隔 N 分鐘自動抓 TWSE/TPEx 最新價寫回現價；跨使用者去重，同一代號只查一次；管理員可在「管理員設定 → 系統設定」開關、調整間隔與手動立即更新\n- **股票交易**：手續費（0.1425%，整股最低 20 元）/ 證交稅（一般股 0.3%、ETF / 權證 0.1%）自動計算可手動覆寫\n- **股利紀錄**：TWSE `TWT49U` / `TWT49UDetail` 除權息自動同步；現金股利對應入款交易帳戶反查\n- **實現損益**：FIFO 全精度（decimal.js）逐筆計算，今年 / 總損益彙總；賣出鏈式約束驗證\n- **定期定額**：遇 TWSE 休市日自動順延；歷史收盤價回填；`(plan_id, period_start_date)` 唯一鍵保 idempotency\n- **下市標記**：凍結最後價格，後續查價自動跳過\n\n### 統計與排程\n\n- **統計報表**：分類圓餅（單 / 雙）+ 月度趨勢 + 每日消費 + 自訂時間範圍；同型前一段對比 pill；圓餅扇區可點擊跳轉\n- **排程寄送**：多筆排程並存（每日 / 每週 / 每月，每月可指定 1–28 日或「最後一天」自動對齊各月天數）；觸發時刻精確到「時:分」（如 23:59）並以使用者時區判斷；寄信通道支援 SMTP / Zeabur Email / Resend，主要通道執行期失敗自動退回備用通道；停用期間漏掉的觸發點不溯及補寄\n\n### 資料治理（v4.28.0）\n\n- **CSV 匯出**：交易 / 分類 / 股票交易 / 股利紀錄；純伺服端 stream + UTF-8 BOM + Formula Injection 防護\n- **CSV 匯入**：互斥鎖（重入回 409）+ 全 DB transaction 原子化 + 進度回饋每 500 筆 + ISO 8601 嚴格驗證 + 多欄重複偵測\n- **整檔備份 / 還原**：PostgreSQL SQL 備份下載 / 上傳；還原失敗自動回滾至 `before-restore-{ts}.sql`；管理員可列管 5 份 / 90 天內備份\n- **稽核日誌**：管理員與使用者可分別檢視；支援過濾、CSV 匯出、清空、保留天數設定（30 / 90 / 180 / 365 / forever）\n\n### 系統管理\n\n- **使用者管理**：管理員可開關註冊、設定 Email 白名單、IP 白名單、新增 / 刪除 / 重設密碼\n- **登入稽核**：時間、IP、國家、方式（密碼 / Google / LINE / Passkey）、成功 / 失敗\n- **寄信通道**：以 `EMAIL_PROVIDER_PRIMARY` / `EMAIL_PROVIDER_FALLBACK` 環境變數指定主要與備用通道（值：`smtp` / `zeabur` / `resend` / 留空），支援 SMTP（Nodemailer）、Zeabur Email（ZSend HTTP API）、Resend；可選 `EMAIL_SENDER_NAME` 為三通道統一指定寄件人顯示名稱；管理員設定頁可即時檢視各通道是否設定並寄送測試信\n- **MEGA S4 備份**：管理員可在「資料匯出匯入」頁將完整 PostgreSQL SQL 備份手動上傳到 MEGA S4 S3 相容 bucket，金鑰僅由伺服器端環境變數讀取\n- **交易照片附件**：新增交易時可附上照片，並選擇存放於 Server 本機或 S3 相容物件儲存；附件取用一律走登入授權 API\n- **路由稽核模式**（v4.29.0）：security（預設）/ extended（含 401 session 失效）/ minimal（路由稽核全部關閉）\n- **API 使用與授權頁**：動態列出所有外部 API 來源、配額、合規授權字樣（IPinfo `IP address data is powered by IPinfo`）\n\n---\n\n## 技術架構\n\n| 層級 | 技術 |\n| ---- | ---- |\n| 前端 | 原生 HTML / CSS / Vanilla JS (已遷移至 Next.js 15 + Tailwind CSS v4) |\n| 後端 | Node.js ≥ 24 + Next.js 16 API Routes |\n| 資料庫 | PostgreSQL |\n| 認證 | JWT（HS256，httpOnly Cookie）+ bcryptjs；選配 Google OAuth Code Flow + LINE Login Code Flow + Passkey（WebAuthn） |\n| 金額精度 | decimal.js（FIFO / 匯率 / 手續費分攤前後端同構共用 `lib/moneyDecimal.js`） |\n| 圖表 | Chart.js |\n| 寄信 | SMTP（Nodemailer）/ Zeabur Email（ZSend HTTP API）/ Resend；以環境變數指定主備通道，執行期 fallback |\n| 安全 | CSP、SRI、CORS 白名單、記憶體內速率限制（`lib/rateLimiter.ts`） |\n| 契約 | OpenAPI 3.2.0（`openapi.yaml`） |\n\n**完全不引入**：前端框架（React / Vue）、router 套件（page.js / Navigo）、Modal 函式庫（micromodal）、focus-trap、icon 字型（Lucide / Heroicons）、build 工具（Vite / esbuild）。(v4.33.0 遷移至 Next.js + Tailwind v4)\n\n---\n\n## 快速啟動\n\n### Docker（推薦）\n\n```bash\ndocker run -d \\\n  --name assetpilot \\\n  --restart unless-stopped \\\n  -p 3000:3000 \\\n  -v assetpilot-data:/app/data \\\n  es94111/assetpilot:latest\n```\n\n開啟 \u003chttp://localhost:3000\u003e 即可使用。`JWT_SECRET` 首次啟動自動產生並寫入 `/app/data/.env`，之後重啟自動讀取。\n\n**Docker Compose 範例：**\n\n```yaml\nservices:\n  postgres:\n    image: postgres:17-alpine\n    restart: unless-stopped\n    environment:\n      - POSTGRES_DB=assetpilot\n      - POSTGRES_USER=assetpilot\n      - POSTGRES_PASSWORD=assetpilot\n    volumes:\n      - assetpilot-postgres:/var/lib/postgresql/data\n\n  assetpilot:\n    image: es94111/assetpilot:latest\n    container_name: assetpilot\n    restart: unless-stopped\n    depends_on:\n      - postgres\n    ports:\n      - \"3000:3000\"\n    volumes:\n      - assetpilot-data:/app/data\n    environment:\n      - DATABASE_URL=postgres://assetpilot:assetpilot@postgres:5432/assetpilot\n      - GOOGLE_CLIENT_ID=         # 選配\n      - LINE_CHANNEL_ID=          # 選配\n      # - ALLOWED_ORIGINS=https://your-domain.com\n\nvolumes:\n  assetpilot-data:\n  assetpilot-postgres:\n```\n\n**映像檔：** [`es94111/assetpilot`](https://hub.docker.com/r/es94111/assetpilot)，支援 `linux/amd64` + `linux/arm64`，基底 `node:24-alpine`，~180 MB，內建每 30 秒 `/api/config` 健康檢查。\n\n### Node.js 直接執行\n\n```bash\nnpm install              # Node.js ≥ 24\ncp .env.example .env     # 依需求編輯\nnpm run dev\n```\n\n**正式環境（build / start）：**\n\n```bash\nnpm run build\nnpm run start\n```\n\n### PostgreSQL\n\nAssetPilot 現在僅支援 PostgreSQL。請設定 `DATABASE_URL` 或 `POSTGRES_URL`，啟動時會自動建立或補齊必要資料表與欄位。\n\n### Synology NAS\n\nContainer Manager → Registry → 搜尋 `es94111/assetpilot` → 下載 → Create → Port `3000:3000` → 啟動。\n自訂網域請用 DSM **控制台 → 登入入口 → 反向代理**，並在自訂標題加入 `X-Forwarded-For: $proxy_add_x_forwarded_for`。\n\n### Volume 備份 / 還原\n\n```bash\n# 備份（含應用設定與 SSL 憑證；PostgreSQL 資料請備份 assetpilot-postgres volume 或使用管理頁 SQL 備份）\ndocker run --rm -v assetpilot-data:/data -v $(pwd):/backup alpine \\\n  tar czf /backup/assetpilot-backup.tar.gz -C /data .\n\n# 還原\ndocker run --rm -v assetpilot-data:/data -v $(pwd):/backup alpine \\\n  tar xzf /backup/assetpilot-backup.tar.gz -C /data\n```\n\n\u003e ⚠️ PostgreSQL 資料位於資料庫 volume；`assetpilot-data` 只保存應用設定與 SSL 憑證等檔案。\n\n---\n\n## 環境變數\n\nDocker 多數參數已有合理預設，重點關注「自動產生」與「功能選配」區塊。Node.js 直接執行請複製 `.env.example`。\n\n| 變數 | 類別 | 說明 | 預設值 |\n| ---- | ---- | ---- | ------ |\n| `PORT` | 基本 | 伺服器埠號 | `3000` |\n| `DATABASE_URL` / `POSTGRES_URL` | 基本 | PostgreSQL 連線字串 | — |\n| `POSTGRES_SYNC_RESULT_BUFFER_BYTES` | 選配 | PostgreSQL 同步相容層單次查詢結果緩衝大小 | `67108864` |\n| `JWT_EXPIRES` | 基本 | JWT 有效期限 | `7d` |\n| `JWT_SECRET` | 🔑 自動 | JWT 簽章金鑰，64 字元 hex（首次啟動自動產生） | — |\n| `ENV_PATH` | 🔑 自動 | 自動產生金鑰的存放路徑 | `/app/data/.env` |\n| `GOOGLE_CLIENT_ID` | SSO | Google OAuth 2.0 Client ID（留空停用 SSO） | — |\n| `GOOGLE_CLIENT_SECRET` | SSO | Google OAuth Client Secret | — |\n| `GOOGLE_OAUTH_REDIRECT_URIS` | SSO | OAuth 重定向 URI 白名單，逗號分隔 | 自動推導 |\n| `LINE_CHANNEL_ID` | SSO | LINE Login Channel ID（留空停用 LINE 登入） | — |\n| `LINE_CHANNEL_SECRET` | SSO | LINE Login Channel Secret | — |\n| `LINE_OAUTH_REDIRECT_URIS` | SSO | LINE Login callback URL 白名單，逗號分隔；會附加內建 Web/App callback | 自動推導 |\n| `LINE_MESSAGING_CHANNEL_SECRET` | LINE Bot | LINE 官方帳號 Messaging API Channel Secret；留空時 fallback 使用 `LINE_CHANNEL_SECRET` | — |\n| `LINE_MESSAGING_CHANNEL_ACCESS_TOKEN` | LINE Bot | LINE 官方帳號 Messaging API Channel access token；設定後啟用 `/api/line/webhook` | — |\n| `ALLOWED_ORIGINS` | 安全 | CORS 白名單，逗號分隔（正式環境建議設定） | — |\n| `ADMIN_IP_ALLOWLIST` | 安全 | 管理員 IP 白名單，逗號分隔，略過速率限制 | — |\n| `EXCHANGE_RATE_API_KEY` | 選配 | exchangerate-api.com Key | `free` |\n| `IPINFO_TOKEN` | 選配 | ipinfo.io Token，提升 IP 查詢配額 | — |\n| `TWSE_MAX_CONCURRENCY` | 選配 | TWSE 並發查詢上限 | `5` |\n| `STOCK_AUTO_UPDATE_ENABLED` | 選配 | 覆寫「股價自動更新」總開關（`false` / `0` 停用；留空則沿用管理員設定，預設啟用） | — |\n| `STOCK_AUTO_UPDATE_INTERVAL_MIN` | 選配 | 覆寫股價自動更新間隔（分鐘，1–1440）；留空則沿用管理員設定 | `10` |\n| `MEGA_S4_ACCESS_KEY_ID` | 選配 | MEGA S4 Access Key ID；設定後可從資料匯出匯入頁手動上傳資料庫備份 | — |\n| `MEGA_S4_SECRET_ACCESS_KEY` | 選配 | MEGA S4 Secret Access Key；僅伺服器端讀取，不回傳前端 | — |\n| `MEGA_S4_BUCKET` | 選配 | MEGA S4 備份目標 bucket | — |\n| `MEGA_S4_REGION` | 選配 | MEGA S4 region：`eu-central-1` / `eu-central-2` / `ca-central-1` / `ca-west-1` | `eu-central-1` |\n| `MEGA_S4_PREFIX` | 選配 | MEGA S4 object key 前綴 | `assetpilot` |\n| `MEGA_S4_ENDPOINT` | 選配 | 自訂 MEGA S4 S3 endpoint；留空依 region 推導 `https://s3.\u003cregion\u003e.s4.mega.io` | — |\n| `TRANSACTION_PHOTO_LOCAL_DIR` | 選配 | 交易照片本機儲存目錄 | `\u003ccwd\u003e/uploads/transaction-photos` |\n| `TRANSACTION_PHOTO_MAX_BYTES` | 選配 | 單張交易照片大小上限 | `10485760` |\n| `TRANSACTION_PHOTO_DEFAULT_STORAGE` | 選配 | LINE 新增交易照片與其他非互動上傳流程的預設儲存位置：`local` / `s3` | `local` |\n| `TRANSACTION_PHOTO_S3_ACCESS_KEY_ID` / `TRANSACTION_PHOTO_S3_SECRET_ACCESS_KEY` | 選配 | 交易照片 S3 相容物件儲存金鑰；未設定時可 fallback 使用 `MEGA_S4_*` | — |\n| `TRANSACTION_PHOTO_S3_BUCKET` / `TRANSACTION_PHOTO_S3_REGION` / `TRANSACTION_PHOTO_S3_ENDPOINT` / `TRANSACTION_PHOTO_S3_PREFIX` | 選配 | 交易照片 S3 相容物件儲存 bucket、region、endpoint 與 key 前綴 | `assetpilot/transaction-photos` |\n| `SSL_CERT` / `SSL_KEY` | 選配 | Cloudflare Origin Certificate 路徑（管理員 UI 可上傳，需重啟套用） | — |\n| `EMAIL_PROVIDER_PRIMARY` | 寄信 | 主要寄信通道：`smtp` / `zeabur` / `resend` / 留空（停用） | — |\n| `EMAIL_PROVIDER_FALLBACK` | 寄信 | 備用寄信通道（同上選項；留空或與 primary 同則不啟用 fallback） | — |\n| `EMAIL_SENDER_NAME` | 寄信 | 寄信人顯示名稱（三通道共用，例：`AssetPilot`）；若 FROM 變數已是 `Name \u003cemail\u003e` 格式則尊重該通道既有設定 | — |\n| `SMTP_HOST` / `SMTP_PORT` / `SMTP_SECURE` / `SMTP_USER` / `SMTP_PASSWORD` / `SMTP_FROM` | 寄信 | SMTP 通道設定（Gmail / Outlook 等；465 走 TLS 設 `SMTP_SECURE=true`，587 走 STARTTLS 設 false） | `SMTP_PORT=587`、`SMTP_SECURE=false` |\n| `ZEABUR_API_KEY` | 寄信 | [Zeabur Email / ZSend](https://zeabur.com/docs/en-US/email/quick-start) API Key | — |\n| `ZEABUR_FROM_EMAIL` | 寄信 | Zeabur Email 寄件人，須為已驗證寄件網域 | — |\n| `RESEND_API_KEY` | 寄信 | [Resend](https://resend.com/api-keys) API Key | — |\n| `RESEND_FROM_EMAIL` | 寄信 | Resend 寄件人，須為已驗證網域信箱 | — |\n| `APP_URL` | 寄信 | 對外網址，用於信件 CTA 按鈕（留空則隱藏） | — |\n\n\u003e 💡 **寄信通道**（v4.30.0 起）一律由環境變數設定。`EMAIL_PROVIDER_PRIMARY` 留空則寄信功能停用；`EMAIL_PROVIDER_FALLBACK` 僅在 primary 執行期失敗時觸發（不重試、不補寄）。對應通道的設定變數需配合補齊（例：選 `smtp` 需設 `SMTP_HOST` 等；選 `zeabur` 需設 `ZEABUR_API_KEY` 與 `ZEABUR_FROM_EMAIL`）。\n\n---\n\n## 反向代理\n\n### Nginx\n\n```nginx\nserver {\n    listen 443 ssl http2;\n    server_name your-domain.com;\n\n    ssl_certificate     /path/to/cert.pem;\n    ssl_certificate_key /path/to/key.pem;\n\n    location / {\n        proxy_pass http://127.0.0.1:3000;\n        proxy_http_version 1.1;\n        proxy_set_header Host              $host;\n        proxy_set_header X-Real-IP         $remote_addr;\n        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;\n        proxy_set_header X-Forwarded-Proto $scheme;\n    }\n}\n```\n\n### Caddy\n\n```\nyour-domain.com {\n    reverse_proxy localhost:3000\n}\n```\n\nCaddy 自動申請並續期 HTTPS 憑證。\n\n### Cloudflare Tunnel\n\n可直接掛 Cloudflare Tunnel 後啟用 API Shield → 上傳 `openapi.yaml` 啟用 schema 驗證。\n\n---\n\n## 認證機制\n\n支援四種登入方式，皆可同時啟用：\n\n### 帳密登入\n\n預設啟用。第一位註冊的使用者自動成為管理員。可由管理員開關「公開註冊」與「Email 白名單」。\n\n### Google SSO（Authorization Code Flow）\n\n1. 至 [Google Cloud Console](https://console.cloud.google.com/) 建立 OAuth 2.0 用戶端 ID（類型：網頁應用程式）\n2. **已授權 JavaScript 來源**：本機 `http://localhost:3000`、正式 `https://your-domain.com`\n3. **已授權重新導向 URI**：本機 `http://localhost:3000/`、正式 `https://your-domain.com/`（含尾端 `/`）；Android App 另需加入 `https://your-domain.com/app/google-callback`\n4. 將 `GOOGLE_CLIENT_ID` 與 `GOOGLE_CLIENT_SECRET` 設為環境變數啟動\n5. 未設定時 Google 登入按鈕自動隱藏，不影響其他登入方式\n\n\u003e ⚠️ 若登入後停在 `/?code=...`，請確認重新導向 URI 與網域完全一致（含 `https://` 與尾端 `/`）。\n\n### LINE Login（Authorization Code Flow）\n\n1. 至 [LINE Developers Console](https://developers.line.biz/console/) 建立 Provider 與 LINE Login channel\n2. 在 LINE Login channel 啟用 LINE Login，並申請 / 啟用 email 權限\n3. **Callback URL**：本機 `http://localhost:3000/auth/line/callback`、`http://localhost:3000/app/line-callback`；正式 `https://your-domain.com/auth/line/callback`、`https://your-domain.com/app/line-callback`\n4. 將 `LINE_CHANNEL_ID`、`LINE_CHANNEL_SECRET` 與 `LINE_OAUTH_REDIRECT_URIS` 設為環境變數啟動\n5. 登入管理員帳號後，到「管理員設定 → 系統設定」勾選「啟用 LINE 登入」並儲存\n6. 未設定或未由管理員啟用時，LINE 登入與綁定按鈕自動隱藏\n\n`LINE_OAUTH_REDIRECT_URIS` 範例：\n\n```env\nLINE_OAUTH_REDIRECT_URIS=https://your-domain.com/auth/line/callback\n```\n\n本機開發：\n\n```env\nLINE_OAUTH_REDIRECT_URIS=http://localhost:3000/auth/line/callback\n```\n\nLINE 登入與綁定皆使用 LINE 官方 Login API v2.1：`/oauth2/v2.1/authorize`、`/token`、`/verify`，並以一次性 state + nonce 防護登入流程。\n\n### LINE 官方帳號記帳\n\n1. 在同一個 LINE Developers Provider 建立或選擇 Messaging API channel，取得 Channel secret 與 long-lived Channel access token\n2. 設定 `LINE_MESSAGING_CHANNEL_SECRET`、`LINE_MESSAGING_CHANNEL_ACCESS_TOKEN`，並設定 `APP_URL=https://your-domain.com`\n3. 在 Messaging API channel 的 Webhook URL 填入 `https://your-domain.com/api/line/webhook`，啟用 Use webhook\n4. 使用者先在 AssetPilot「帳號設定」綁定 LINE 帳號，之後可在官方帳號輸入 `選單`，用 Flex Message Button 選擇「新增支出」「新增收入」「查看紀錄」\n\nWebhook 回覆使用 LINE Flex Message Button：未綁定時顯示「綁定 LINE 帳號」URI button；綁定後顯示主選單。按「新增支出」或「新增收入」後，再輸入 `120 午餐`、`5000 薪資` 這類金額與備註即可新增；按「查看紀錄」可查今天、昨天或本月。仍支援直接輸入 `支出 120 午餐`、`收入 5000 薪資`、`查詢 本月`。\n\n資產統計報表排程支援 Email / LINE 兩種通知方式；管理員可在「管理員設定 → 報表排程」新增排程時勾選 LINE 通知。LINE 排程通知會推送同 email 報表相近的資產摘要 Flex Message，使用者需先完成 LINE 綁定。\n\n同頁也可新增「LINE 支出提醒」，用每日 / 每週 / 每月排程（每月同樣可選指定日期或「最後一天」、觸發時刻精確到分）提醒使用者記錄支出。提醒訊息會提供「新增支出」按鈕，點擊後依 `金額 備註 日期（日期可省略）` 的規則輸入即可記帳。\n\n### Passkey（WebAuthn）\n\n登入後可至「個人設定 → 安全」綁定。支援 Touch ID / Face ID / Windows Hello / 硬體金鑰；採 `@passwordless-id/webauthn` 純前端實作 + 後端驗證。\n\n---\n\n## 使用指南\n\n### 首次使用\n\n1. 開啟 `http://localhost:3000` → **立即註冊**\n2. **第一位註冊的使用者自動成為管理員**\n3. 系統自動建立預設分類（食、衣、住、行 ...）與預設帳戶（現金、銀行帳戶）\n\n### URL 直連\n\n任何頁面皆可直接以網址訪問或加入書籤；F5 重整不掉頁；瀏覽器上一頁 / 下一頁完整還原（含捲動位置）。未登入訪客被導向 `/login?next=\u003c原 URL\u003e`，登入成功後自動跳回。\n\n| URL | 說明 |\n| --- | --- |\n| `/` | 公開首頁 |\n| `/login` | 登入頁 |\n| `/dashboard` | 儀表板 |\n| `/finance/transactions`, `/finance/reports`, `/finance/budget`, `/finance/accounts`, `/finance/categories`, `/finance/recurring` | 收支管理 |\n| `/stocks`, `/stocks/transactions`, `/stocks/dividends`, `/stocks/realized` | 股票投資 |\n| `/settings/account`, `/settings/admin`, `/settings/export` | 設定（admin 僅管理員可見） |\n| `/api-credits` | API 使用與授權 |\n| `/privacy`, `/terms` | 隱私權政策 / 服務條款 |\n\n### 信用卡繳費\n\n使用信用卡消費時記為「**支出**」+ 信用卡帳戶（餘額顯示負數=真實負債）。繳費時類型選「**轉帳**」，從銀行帳戶轉至信用卡帳戶，即可扣除銀行餘額並沖銷信用卡負數，不重複計算為支出。\n\n### 股票\n\n- **新增持股**：輸入股票代號（如 `2330`），系統自動從 TWSE 查詢帶入名稱與現價；手續費、交易稅自動計算可手動修改\n- **更新股價**：三段策略 — 盤中 TWSE 即時、盤後 `STOCK_DAY` 今日收盤、其他時段 `STOCK_DAY_ALL` 備援；可手動更新，或由伺服器在交易時段內依管理員設定的間隔自動更新\n- **同步除權息**：依持股期間自動新增現金 / 股票股利，不重複；阻擋式 Modal 含進度條與取消按鈕\n\n### CSV 匯出匯入\n\n| 類型 | 欄位 |\n| ---- | ---- |\n| 交易記錄 | 日期、類型、金額、幣別、分類、帳戶、備註 |\n| 分類 | 父分類 / 子分類、類型（收入 / 支出）、顏色 |\n| 股票交易 | 日期、代號、名稱、類型、股數、成交價、手續費、交易稅、帳戶、備註 |\n| 股利紀錄 | 日期、代號、名稱、現金股利、股票股利、備註 |\n\n匯入時若股票代號不存在會自動建立；名稱不正確會以 CSV 內的名稱更新。匯入採互斥鎖 + DB transaction 原子化，發生錯誤會整批回滾。\n\n---\n\n## 資料治理\n\n### 資料操作稽核日誌（v4.28.0）\n\n每次 CSV 匯出 / 匯入、整檔備份 / 還原皆寫入 `data_operation_audit_log`，欄位含使用者、IP、UA、時間、結果、metadata（如 imported / skipped / errors / byteSize）。\n\n- 一般使用者可至「個人設定 → 我的操作紀錄」檢視自己的歷史（後端強制 user_id 覆寫）\n- 管理員可至「管理員 → 資料操作稽核日誌」檢視全部，支援過濾、CSV 匯出、清空、保留天數調整（30 / 90 / 180 / 365 / forever）\n\n### 路由稽核模式（v4.29.0）\n\n`system_settings.route_audit_mode`：\n\n| 模式 | 寫入事件 |\n| --- | --- |\n| `security`（預設） | admin path blocked / open redirect blocked / static path traversal blocked |\n| `extended` | security 範圍 + 401 session_expired |\n| `minimal` | 本功能定義之路由稽核全部不寫入；既有 007 稽核行為不受影響 |\n\n切換立即生效，不需重啟。\n\n### 整檔備份 / 還原\n\n- **下載備份**：管理員可下載當前資料庫（檔名 `assetpilot-backup-{YYYYMMDDHHmmss}.db`）；按下下載前彈出敏感資料警示確認 Modal\n- **還原**：上傳備份檔；通過驗證後寫入 `backups/before-restore-{ts}.db`；替換失敗自動回滾並回 `422 RESTORE_FAILED_ROLLED_BACK`\n- **自動備份保留**：保留最近 5 份且 ≤ 90 天；管理員可手動刪除（雙重防路徑遍歷：`path.basename` + regex）\n\n### 交易照片附件\n\n- 新增交易時可選擇最多 5 張照片，每張預設 10 MB；手機網頁提供「拍照」與「選擇圖片」兩個入口，可直接呼叫相機或從相簿上傳。\n- 儲存位置可選 Server 本機或 S3 相容物件儲存；S3 未設定時 UI 會停用該選項。\n- LINE 新增記錄流程中可直接傳送照片；照片會暫存在 LINE message id，確認新增後再下載並附到該筆交易。LINE 端使用 `TRANSACTION_PHOTO_DEFAULT_STORAGE` 決定本機或 S3。\n- 照片 metadata 存於 `transaction_attachments`，實際檔案不放 public 目錄，讀取需通過 `/api/transactions/{txId}/attachments/{attachmentId}/file` 權限檢查。\n\n---\n\n## 安全性\n\n| 機制 | 說明 |\n| ---- | ---- |\n| 資料庫加密 | ChaCha20-Poly1305 AEAD + PBKDF2-SHA256 金鑰推導 |\n| 密碼加密 | bcryptjs 雜湊儲存，不明文保存 |\n| XSS 防護 | 所有使用者輸入經 `escHtml()` 跳脫後才插入 DOM |\n| 安全標頭 | Next.js 回應標頭設定（HSTS、X-Content-Type-Options、Referrer-Policy） |\n| CSP | 限制 inline script 與外部資源來源 |\n| 速率限制 | 登入 / 註冊每 IP 每 15 分鐘最多 20 次；公開頁面每分鐘最多 120 次 |\n| Cloudflare API Shield | OpenAPI 3.2.0 Schema（`openapi.yaml`），可啟用請求驗證 |\n| CORS 控制 | `ALLOWED_ORIGINS` 白名單 |\n| OAuth 防 CSRF | Google / LINE 登入使用一次性 state；LINE 額外使用 nonce 驗證 ID Token |\n| `?next=` 開放重定向防護 | 5 條規則白名單：相對路徑 / 拒 protocol-relative / 拒 `://` / pathname 必須命中前端 ROUTES 表 |\n| 路徑遊走偵測 | catch-all 偵測 `..` / `%2e%2e` / `%252e%252e`；寫稽核日誌 |\n| Admin path 攔截 | 後端維護 `ADMIN_ONLY_PATHS` 常數陣列；非管理員命中時寫稽核並由前端渲染 404 訊息頁 |\n| Formula Injection 防護 | CSV 匯出對所有以 `=` `+` `-` `@` 開頭的欄位前置撇號 |\n| SRI | 外部 CDN 腳本（Font Awesome、Chart.js、decimal.js）完整性驗證 |\n| 登入稽核 | 記錄時間、IP、國家、方式；管理員可查失敗嘗試與帳號鎖定狀態 |\n\n---\n\n## 外部 API 來源\n\n| 服務 | 用途 | 連結 |\n| ---- | ---- | ---- |\n| TWSE OpenAPI | 台股即時股價、收盤、除權息 | \u003chttps://openapi.twse.com.tw/\u003e |\n| exchangerate-api.com | 全球即時匯率（基礎 TWD） | \u003chttps://www.exchangerate-api.com/\u003e |\n| Google Identity Services | Google SSO 登入 | \u003chttps://developers.google.com/identity\u003e |\n| LINE Login API | LINE 登入與帳號綁定 | \u003chttps://developers.line.biz/en/docs/line-login/\u003e |\n| IPinfo Lite | IP 國家查詢（合規授權字樣固定顯示於 API 使用頁） | \u003chttps://ipinfo.io/lite\u003e |\n| SMTP（Nodemailer） | 排程信件 / 系統通知（Gmail / Outlook 等） | \u003chttps://nodemailer.com/\u003e |\n| Zeabur Email（ZSend） | 排程信件 / 系統通知 | \u003chttps://zeabur.com/docs/en-US/email/quick-start\u003e |\n| Resend | 排程信件 / 系統通知 | \u003chttps://resend.com/\u003e |\n| MEGA S4 Object Storage | 管理員整檔 PostgreSQL SQL 備份的 S3 相容物件儲存目的地 | \u003chttps://mega.io/zh-hant/objectstorage\u003e |\n\n完整列表與授權字樣可在執行中應用程式的 `/api-credits` 頁面查看。\n\n---\n\n## 專案治理\n\n本專案採用 [Spec-Driven Development](https://github.com/github/spec-kit) 工作流程。每個功能皆有完整的 spec → research → plan → contracts → tasks → implementation 軌跡，存於 `specs/\u003cNNN-feature-name\u003e/` 下。\n\n| 工件 | 用途 |\n| --- | --- |\n| `spec.md` | 使用者導向的功能規格與驗收條件 |\n| `research.md` | 技術選型決策記錄 |\n| `plan.md` | 實作計畫與檔案影響面 |\n| `data-model.md` | 資料模型與 schema 變更 |\n| `contracts/*.openapi.yaml` | API 契約 delta（OpenAPI 3.2.0）|\n| `quickstart.md` | 手動驗證劇本 |\n| `tasks.md` | 可執行任務清單 |\n| `checklists/` | 上線前檢查表 |\n\n專案憲章：[`.specify/memory/constitution.md`](.specify/memory/constitution.md)\n完整變更歷史：[`changelog.json`](changelog.json) | 軟體需求規格：[`SRS.md`](SRS.md)\n\n---\n\n## 授權\n\n[GNU AGPL v3](LICENSE)\n\n\u003e 本軟體採 AGPL v3 授權；**只要任何人能透過網路使用您運行的這個程式（即使您只是自托管給朋友家人用）**，您就有義務以同樣 AGPL v3 條款公開您的修改原始碼。商業授權請另行洽談。\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fes94111%2Fassetpilot","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fes94111%2Fassetpilot","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fes94111%2Fassetpilot/lists"}