Skip to content

add: URL ストック機能(IndexedDB プロトタイプ) #117 - #123

Draft
kuu13580 wants to merge 1 commit into
developfrom
feature/issue117-url-stock
Draft

add: URL ストック機能(IndexedDB プロトタイプ) #117#123
kuu13580 wants to merge 1 commit into
developfrom
feature/issue117-url-stock

Conversation

@kuu13580

Copy link
Copy Markdown
Contributor

Summary

  • 旅程ページ編集モードに参考 URL ストック機能を追加(issue 【フロントエンド】別のWebページを簡単にストックできるようにしたい #117 のデモ用 PR)
  • サーバー側は /preview(メタ取得)と /format(Gemini で markdown 整形)の ステートレス 2 本のみ。永続化はしない
  • フロント側で IndexedDB(Dexie v3 の tripUrls テーブル)に保存し、DB マイグレーションを回避
  • AI 整形は memo とは別の専用テキストエリアで「整形対象 + 指示」を受け取り、結果を memo 末尾に追記

主要な変更

Backend (server/)

  • routers/trip_urls.py: POST /trips/{trip_id}/urls/preview / /format
  • services/url_extractor.py: extract_url_metadata + format_text_with_gemini(システムプロンプト固定)
  • schemas/trip_url.py: I/O スキーマのみ。永続化用モデル・migration なし
  • config.py: GEMINI_API_KEY / gemini_model 設定(gemini-2.5-flash-lite
  • 依存追加: trafilatura / httpx / google-genai
  • dev 環境のみ Gemini req/res を /tmp/tabishare-gemini-debug/*.json へ書き出し

Frontend (frontend/)

  • dialogs/AddTripUrlDialog.tsx: URL + memo(markdown)+ AI 整形セクション
  • dialogs/EditTripUrlDialog.tsx: 編集 + AlertDialog 経由の削除
  • components/tripUrl/TripUrlFormatSection.tsx: 整形対象 + 指示 → AI 結果を memo 末尾に追記
  • components/tripUrl/TripUrlList(Item).tsx: 編集モード時のみ表示
  • hooks/useTripUrls.ts: CRUD は IndexedDB、/preview /format はサーバー API
  • lib/db.ts: Dexie v3 に tripUrls: '++id, tripId, createdAt' 追加
  • lib/debugCache.ts: Gemini レスポンスのデバッグキャッシュ汎用ユーティリティ
    • local 既定 ON / stg・prod 常時 OFF / 24h TTL
    • ?debug=true で Jotai atom を立てて強制リフレッシュ
    • HIT 時は console.warn でログ
  • pages/TripPage.tsx: 編集モードに <TripUrlList tripId={trip.id} /> を統合

設計メモ(議論ログ)

  • 当初は「ページ全体を fetch + AI 要約」だったが、SPA で fetch が壊れがち + ユーザーは意図した部分だけ整形したいことから「整形対象貼付 + 指示」モデルに転換
  • 永続化を IndexedDB に倒したのは、デモ段階では マイグレーション運用を回避したいため。本実装フェーズでは backend 永続化に戻す前提
  • 詳細は issue 【フロントエンド】別のWebページを簡単にストックできるようにしたい #117 のコメントを参照

Test plan

  • cd server && pnpm dotenvx run -- uv run pytest(既存 91 件パス済)
  • cd frontend && pnpm test(既存 72 件パス済)
  • 編集モードで URL ストックを追加できる(メタ情報取得、サムネ表示)
  • memo を手書きのみで保存できる(AI 整形なし)
  • AI 整形: 整形対象テキストを貼り付けて「AI で整形」→ memo 末尾に markdown が追記される
  • 編集ダイアログでも AI 整形が動作する
  • 削除(AlertDialog 確認後)
  • リロード後も IndexedDB に保存された一覧が復元される
  • ?debug=true で同一入力でも Gemini が再実行される(HIT ログが消える)
  • ?debug=false または param なしリロードで通常キャッシュ動作に戻る

既知のスコープ外

  • DB 永続化(このブランチでは行わない)
  • 並び替え・カテゴリ・タグ
  • ブックマークレット / Web Share Target
  • 同一 URL の trip 間共有

🤖 Generated with Claude Code

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

本 PR は、旅程ページに URL をストックする機能を導入するものです。プロトタイプ段階として、データ永続化をサーバー側ではなくフロントエンドの IndexedDB に倒すことで、マイグレーションコストを抑えた設計としています。また、Gemini を利用したテキストの AI 整形機能もあわせて実装されており、ユーザーのメモ作成を効率化します。

Highlights

  • URL ストック機能の追加: 旅程ページの編集モードに、URL を保存・管理できるストック機能を追加しました。
  • IndexedDB によるフロントエンド永続化: DB マイグレーションを回避するため、URL データの永続化にはフロントエンドの IndexedDB (Dexie) を採用しました。
  • AI 整形機能の導入: Gemini API を活用し、ユーザーが貼り付けたテキストを Markdown 形式に整形する機能を追加しました。
  • ステートレスなバックエンド API: メタデータ取得用 /preview と AI 整形用 /format の 2 つのステートレスなエンドポイントを実装しました。
New Features

🧠 You can now enable Memory (public preview) to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a "URL Stock" feature that allows users to save URLs to a trip, fetch metadata like titles and thumbnails, and use Gemini AI to format pasted text into markdown notes. The implementation uses IndexedDB (Dexie) for local data persistence during the MVP phase and includes a debug caching utility for local development. Review feedback highlighted a critical SSRF vulnerability in the URL fetching logic and suggested several performance optimizations, such as reusing HTTP and AI client instances. Additionally, improvements were recommended for type safety in database operations and more granular error handling for the AI service integration.

# ---------------------------------------------------------------------------


async def _fetch_html(url: str) -> str | None:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

ユーザーから提供された URL をそのまま fetch することは、SSRF (Server-Side Request Forgery) の脆弱性につながります。プライベート IP アドレスや予約済み IP アドレスへのアクセスを制限するバリデーションを追加することを強く推奨します。

Comment on lines +84 to +92
const id = await db.tripUrls.add({
tripId,
url: validated.url,
title: validated.title ?? null,
thumbnailUrl: validated.thumbnailUrl ?? null,
memo: validated.memo ?? null,
createdAt: now,
updatedAt: now,
} as TripUrlEntry);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Dexie の add メソッドに渡すオブジェクトに id が含まれていないため、TripUrlEntry へのキャストが型安全ではありません。TripUrlEntryid をオプショナルにするか、Omit<TripUrlEntry, 'id'> を使用してキャストを避けるべきです。

Suggested change
const id = await db.tripUrls.add({
tripId,
url: validated.url,
title: validated.title ?? null,
thumbnailUrl: validated.thumbnailUrl ?? null,
memo: validated.memo ?? null,
createdAt: now,
updatedAt: now,
} as TripUrlEntry);
const id = await db.tripUrls.add({
tripId,
url: validated.url,
title: validated.title ?? null,
thumbnailUrl: validated.thumbnailUrl ?? null,
memo: validated.memo ?? null,
createdAt: now,
updatedAt: now,
} as Omit<TripUrlEntry, 'id'>);

Comment thread frontend/src/lib/db.ts
*/
export interface TripUrlEntry {
/** Dexie 自動採番の主キー */
id: number;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

IndexedDB (Dexie) で ++id を使用して自動採番する場合、新規作成時には id が存在しないため、インターフェース定義では id をオプショナルにするのが適切です。

Suggested change
id: number;
id?: number;

async def _fetch_html(url: str) -> str | None:
"""URL を fetch して HTML 文字列を返す。失敗時は None"""
try:
async with httpx.AsyncClient(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

httpx.AsyncClient をリクエストごとに生成するのは非効率です。コネクションプールを有効活用するために、グローバルなクライアントインスタンスを再利用するか、FastAPI の Dependency Injection を利用してライフサイクル管理を行うべきです。

}

try:
client = genai.Client(api_key=api_key)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

genai.Client のインスタンス化を関数内で行うと、呼び出しのたびにオーバーヘッドが発生します。API キーが設定ファイルから取得されるのであれば、モジュールレベルで一度だけ初期化するか、シングルトンパターンで再利用するように改善してください。

if log_path is not None:
logger.info("Gemini debug log: %s", log_path)
return formatted or None
except Exception as exc: # SDK が投げる例外は多岐に渡るため広めに捕捉

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

広範な Exception をキャッチして None を返すのではなく、google-genai SDK が投げる可能性のある具体的な例外(認証エラー、レート制限、ネットワークエラーなど)を個別にハンドリングし、適切なログ出力やエラーレスポンスを行うべきです。

旅程ページ編集モードに、参考にしたい外部 URL を memo 付きで
ストックできる機能を追加。MVP 検証中は DB マイグレーションを避け、
永続化はフロント側 IndexedDB のみで行う。

サーバー(ステートレス、永続化なし)
- POST /trips/{trip_id}/urls/preview: URL から title / og:image を取得
- POST /trips/{trip_id}/urls/format:   貼付テキスト + 指示を Gemini で
  GitHub Flavored Markdown に整形
- 依存追加: trafilatura / httpx / google-genai
- GEMINI_API_KEY を dotenvx 暗号化で .env / .env.stg / .env.prod に追加

フロント
- AddTripUrlDialog / EditTripUrlDialog: URL + memo(markdown)の自由記述
- TripUrlFormatSection: 整形対象テキスト + 指示 → AI 整形結果を memo 末尾に追記
- TripUrlList / TripUrlListItem: 一覧表示(編集モードのみ)
- IndexedDB (Dexie v3) の tripUrls テーブルで CRUD を実装
- Gemini デバッグキャッシュ: local 既定 ON / stg・prod 常時 OFF。
  ?debug=true で Jotai atom を立てて SPA セッション中は強制リフレッシュ、
  HIT 時は console.warn でログ出力

Refs #117

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@kuu13580
kuu13580 force-pushed the feature/issue117-url-stock branch from c4831f8 to 660961e Compare May 10, 2026 06:21
@github-actions

Copy link
Copy Markdown

Visit the preview URL for this PR (updated for commit 660961e):

https://tabi-share-8ef6b--pr123-feature-issue117-url-gd755zd5.web.app

(expires Sun, 17 May 2026 06:22:59 GMT)

🔥 via Firebase Hosting GitHub Action 🌎

Sign: 9f2a87ede127df7673322845e34cf22c1372d720

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant