支払額だけでなく、次の更新日、解約期限、利用頻度、使い続ける理由まで一緒に管理する個人用サブスク台帳です。
Vite + React + TypeScript + Tailwind CSSで画面を構築し、Cloudflare WorkersのAPIとD1(SQLite互換)にデータを保存します。公開URLはCloudflare Accessで本人だけに制限し、毎朝9時(日本時間)にTelegram通知を判定します。
- サブスクの追加・編集・削除、検索
- 日次・週次・月次・四半期・年次と任意間隔の月額換算
- 割り勘人数、自動更新、解約予告日数、ステータスの管理
- カテゴリ別支出、30日以内の更新、納得度による見直し候補
- 価格変更履歴(料金を変更すると適用日とメモを保存)
- 更新日または解約期限の7日前・3日前・1日前・当日にTelegram通知
- JSONバックアップの書き出しと全件復元
- WebMCP対応ブラウザ向けの一覧取得・追加ツール
ブラウザはD1へ直接接続しません。Reactから同一オリジンの/api/*を呼び、WorkerだけがD1とTelegram Secretへアクセスします。本番のworkers.devではCloudflare Accessの認証コンテキストがないAPIリクエストを401で拒否するため、Access設定が完了するまでD1の内容は取得・変更できません。
Browser (React SPA)
└─ /api/* → Cloudflare Worker
├─ D1 (SQLite互換)
└─ Telegram Bot API (Cron / 手動テスト)
D1は手元の.sqliteファイルをアップロードして常駐させる方式ではなく、Cloudflareが管理するSQLite互換DBです。ローカル開発時はWranglerがD1を.wrangler/以下へエミュレートします。DBの読み取りでは次回更新日を書き換えず、請求基準日から都度決定論的に算出します。
必要なものはNode.js 22以降、pnpm、Cloudflareアカウントです。本番通知を使う場合だけTelegram Botも必要です。
pnpm install
pnpm run types
pnpm run db:migrate
pnpm run db:seed
pnpm run devhttp://127.0.0.1:5173を開きます。db:seedは3件のサンプルをINSERT OR IGNOREで投入するため、再実行しても同じサンプルは重複しません。
Telegramをローカルで試す場合は、追跡されない.dev.varsを作ります。
cp .dev.vars.example .dev.vars値を埋めた後、画面下部の「Telegramをテスト」を実行します。BotトークンはURLに含まれるため、エラーやログへ出さないでください。
pnpm run lint
pnpm run test
pnpm run build- 通常の単体テストは日付境界、月額換算、タイムゾーンを確認します。
- WorkerテストはCloudflare公式Vitest環境と隔離D1で、CRUD、価格履歴、競合更新、入力検証、バックアップ、通知重複防止を確認します。
buildはWorkerとSPAの両方を生成します。
Cloudflare DashboardのWorkers & Pagesでsubsqを開き、Accessタブから「すべてのトラフィック」をCloudflareアカウントメンバーだけに許可します。これで本番URLとPreview URLが保護されます。アプリ側には独自パスワードやユーザーテーブルを持ちません。
pnpm exec wrangler login
pnpm exec wrangler d1 create subsq-db新しいCloudflareアカウントへ移す場合は、表示されたdatabase_idでwrangler.jsoncを更新します。現在の本番D1は作成・設定済みです。その後、型を再生成して本番DBへマイグレーションを適用します。
pnpm run types
pnpm run db:migrate:remoteサンプルから始める場合だけ、次を明示的に実行します。
pnpm run db:seed:remoteTelegramの@BotFatherでBotを作り、そのBotとの個人チャットを一度開始します。Bot APIのgetUpdatesで自分のchat IDを確認し、値をCloudflare Secretへ登録します。
pnpm exec wrangler secret put TELEGRAM_BOT_TOKEN
pnpm exec wrangler secret put TELEGRAM_CHAT_IDトークンとchat IDはwrangler.jsoncや.dev.vars.exampleへ書かないでください。
pnpm run deploywrangler.jsoncのCron 0 0 * * *はUTCで実行され、日本時間の毎朝9時に当たります。デプロイ後はAccessが本番URLとPreview URLの両方を保護していることを確認してからデータを入力し、「Telegramをテスト」を1回実行します。Access未設定時は静的な画面枠だけが表示され、/api/*はACCESS_REQUIREDを返します。
Telegramが明示的なエラーを返した場合はfailedとして保存し、画面から手動再送できます。タイムアウトや接続切断で送信結果が分からない場合はunknownとして保存し、自動再送しません。Telegram APIにはこの用途で安全に使える冪等キーがないため、重複通知を避ける方を優先しています。
同じ日本日付の定期通知は最大1回で、その日に対象となる契約を1通へまとめます。Secretが未設定なら失敗履歴が残ります。
画面の「バックアップを書き出す」は契約、価格履歴、通知設定をJSONへ保存します。「バックアップを復元」はファイル全体を検証してから現在の契約と通知履歴を置き換えます。復元前に最新バックアップを書き出してください。
D1自体のSQLバックアップも取得できます。
pnpm exec wrangler d1 export subsq-db --remote --output=subsq-d1-backup.sql機能設計の検討にはローカルのSubTrackr clone(commit 5bb06a0)を参照しました。特に請求周期、割り勘、解約予告日数、複数通知窓、検索導線を比較しています。参照先はAGPL-3.0のため、ソースコード、テンプレート、文言、画像はコピーせず、本プロジェクトの実装は独自に作成しています。