Skip to content

fix#6

Open
takuto-san wants to merge 1 commit into
goofmint:mainfrom
takuto-san:develop
Open

fix#6
takuto-san wants to merge 1 commit into
goofmint:mainfrom
takuto-san:develop

Conversation

@takuto-san
Copy link
Copy Markdown

@takuto-san takuto-san commented May 30, 2026

Summary by CodeRabbit

リリースノート

  • 新機能

    • ブックマークの編集機能を追加。タイトル、説明、タグの編集が可能になりました
    • ブックマークの削除機能を追加
    • ブックマークカードに編集・削除ボタンを表示
  • スタイル

    • ブックマークカードのUI/UXを改善
    • モバイル端末での表示最適化
  • テスト

    • 編集・更新・削除機能の統合テストカバレッジを追加

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 30, 2026

📝 Walkthrough

Walkthrough

このPRはブックマーク管理アプリケーションに編集・更新・削除機能を追加します。Repositoryに単一取得・更新・削除の各メソッドを実装し、Controllerに対応するエンドポイント、入力正規化・バリデーションロジックを追加、HTMLテンプレートとCSSスタイルを新規追加整備し、統合テストで主要フローをカバーしています。

Changes

ブックマーク編集・更新・削除機能の追加

Layer / File(s) Summary
Repository層のCRUD実装
src/main/java/com/example/bookmark/BookmarkRepository.java
findByIdupdatedeleteメソッドを追加し、BOOKMARK_ROW_MAPPERを統一的に使用してOptionalとbooleanで結果を返します。
Controllerのエンドポイント・検証ロジック
src/main/java/com/example/bookmark/BookmarkController.java
/bookmarks/{id}/edit(GET)、/bookmarks/{id}(POST更新)、/bookmarks/{id}/delete(POST削除)エンドポイントを実装し、入力正規化(trim、タグ整形)と検証(必須性・文字数上限)を行い、エラー時はflash属性でedit画面へリダイレクトします。
UIテンプレートの追加
src/main/resources/templates/edit.html, src/main/resources/templates/index.html
編集フォーム専用テンプレートedit.htmlを新規追加し、index.htmlに編集リンク・削除フォーム(確認ダイアログ付き)、タグ分割表示を追加しました。
スタイルの更新
src/main/resources/static/styles.css
編集フォーム、アイコンアクションボタン(ホバー・危険状態)、タグチップ表示、モバイルレスポンシブ対応を新規追加実装しました。
統合テスト
src/test/java/com/example/bookmark/BookmarkControllerTest.java
編集画面表示、一覧表示、更新(正規化・リダイレクト・DB確認)、バリデーション(空白タイトル・長すぎる説明)、削除動作をMockMcとSQLiteで検証しました。

Sequence Diagrams

sequenceDiagram
  participant User
  participant EditController
  participant BookmarkRepository
  participant Database
  User->>EditController: GET /bookmarks/{id}/edit
  EditController->>BookmarkRepository: findById(id)
  BookmarkRepository->>Database: SELECT * FROM bookmarks WHERE id
  Database-->>BookmarkRepository: Bookmark
  EditController->>EditController: モデル属性を設定<br/>(title/description/tags/bookmark)
  EditController-->>User: edit.html表示
  User->>EditController: POST /bookmarks/{id}<br/>(title, description, tags)
  EditController->>EditController: normalize(title/description/tags)
  EditController->>EditController: validate(form)<br/>- title必須チェック<br/>- 文字数上限チェック
  alt バリデーション成功
    EditController->>BookmarkRepository: update(id, title, description, tags)
    BookmarkRepository->>Database: UPDATE bookmarks SET ...
    Database-->>BookmarkRepository: 更新件数
    EditController-->>User: redirect to index
  else バリデーション失敗
    EditController-->>User: redirect /bookmarks/{id}/edit<br/>(エラーとInput値をFlashに保持)
  end
  User->>EditController: POST /bookmarks/{id}/delete
  EditController->>BookmarkRepository: delete(id)
  BookmarkRepository->>Database: DELETE FROM bookmarks WHERE id
  Database-->>BookmarkRepository: boolean (success/fail)
  EditController-->>User: redirect to index
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • goofmint/bookmark-app-java#1: 同じBookmarkControllerBookmarkRepositoryを拡張し、既存のindex/add機能に対して本PRの編集・更新・削除・検証ロジックを追加する形で連続するコード経路を共有しています。

Poem

🐇 ウサギがブックマークを並べ直し、
編集と削除のボタンを加えた。
タグをスタイリッシュに、テストで安心、
一覧は更に見やすくなりました。
✨ 管理機能、完成!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive プルリクエストのタイトル「fix」は曖昧で、変更内容の具体的な情報を伝えていません。 タイトルを「Add bookmark edit, update, and delete endpoints」など、変更の主要な目的を具体的に説明する内容に変更してください。
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/main/java/com/example/bookmark/BookmarkController.java (1)

119-161: 💤 Low value

初学者向けに正規化・検証ロジックへ意図を示すコメントを補足してはいかがでしょうか。

normalize / validate / normalizeTags は読み手によっては挙動(空文字→null化、タグの分割・空要素除去・再結合、コードポイント単位の文字数カウント)が直感的に分かりにくい箇所です。デモアプリとして読まれることを踏まえ、各メソッドの目的を一言ずつコメントで補うと理解の助けになります。

♻️ コメント補足の例
+    // タグ文字列をカンマ区切りで分割し、空白のみの要素を除いて再結合する
     private static String normalizeTags(String tags) {
         if (tags == null) {
             return null;
         }

As per coding guidelines: 「可読性: 初学者を対象としているため、意図が伝わる十分なコメントがあるか確認し、不足していれば補足を提案する。」

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/com/example/bookmark/BookmarkController.java` around lines 119
- 161, Add brief explanatory comments above the methods normalize, validate,
normalizeTags, trimToNull, and codePointLength that state each method's intent
and key behaviors (e.g., normalize: trim inputs and convert empty strings to
null; validate: required title and max lengths counted in code points;
normalizeTags: split by comma, trim each tag, remove empties, rejoin or return
null; trimToNull: trim and convert empty to null; codePointLength: count
characters in code points). Place one-line comments describing inputs, outputs,
and any non-obvious behaviors (empty->null, code-point based length) so readers
and beginners can quickly understand the logic without changing method code.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/test/java/com/example/bookmark/BookmarkControllerTest.java`:
- Line 20: The test hardcodes the SQLite file under /private/tmp in the
`@SpringBootTest` properties on class BookmarkControllerTest; change the
annotation so the datasource URL uses the JVM temp dir placeholder instead (e.g.
jdbc:sqlite:${java.io.tmpdir}/bookmark-controller-test.db) so it becomes
environment-independent; update the `@SpringBootTest`(properties=...) entry on
BookmarkControllerTest to reference ${java.io.tmpdir} instead of /private/tmp
and run tests to verify the DB file lands in the platform temp directory.

---

Nitpick comments:
In `@src/main/java/com/example/bookmark/BookmarkController.java`:
- Around line 119-161: Add brief explanatory comments above the methods
normalize, validate, normalizeTags, trimToNull, and codePointLength that state
each method's intent and key behaviors (e.g., normalize: trim inputs and convert
empty strings to null; validate: required title and max lengths counted in code
points; normalizeTags: split by comma, trim each tag, remove empties, rejoin or
return null; trimToNull: trim and convert empty to null; codePointLength: count
characters in code points). Place one-line comments describing inputs, outputs,
and any non-obvious behaviors (empty->null, code-point based length) so readers
and beginners can quickly understand the logic without changing method code.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 278f0c6a-ef75-4d0a-b2af-2156c7009d43

📥 Commits

Reviewing files that changed from the base of the PR and between 700e006 and f5992c1.

⛔ Files ignored due to path filters (2)
  • src/main/.DS_Store is excluded by !**/.DS_Store
  • src/test/.DS_Store is excluded by !**/.DS_Store
📒 Files selected for processing (6)
  • src/main/java/com/example/bookmark/BookmarkController.java
  • src/main/java/com/example/bookmark/BookmarkRepository.java
  • src/main/resources/static/styles.css
  • src/main/resources/templates/edit.html
  • src/main/resources/templates/index.html
  • src/test/java/com/example/bookmark/BookmarkControllerTest.java

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.web.servlet.MockMvc;

@SpringBootTest(properties = "spring.datasource.url=jdbc:sqlite:/private/tmp/bookmark-controller-test.db")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Spring Boot @SpringBootTest properties attribute system property placeholder resolution java.io.tmpdir

💡 Result:

In Spring Boot tests, property placeholders (such as ${java.io.tmpdir}) used within the properties attribute of @SpringBootTest (or @TestPropertySource) are resolved against the Spring Environment [1][2]. Key details regarding this mechanism include: 1. Environment Resolution: When you define inlined properties using the properties attribute, these key-value pairs are added to the Spring Environment as a test PropertySource [1][3]. Spring's PropertySourcesPropertyResolver is responsible for resolving placeholders like ${java.io.tmpdir} by looking up the corresponding value in the aggregated Environment [4]. 2. Precedence: Properties defined via the properties attribute of @SpringBootTest or @TestPropertySource have higher precedence than those loaded from application properties files or standard system properties [1][3]. This allows you to selectively override values during testing [1]. 3. Common Pitfalls: - Build Tool Interference: If you are using build tools like Maven or Gradle with resource filtering enabled, placeholders using the ${...} syntax may be expanded by the build tool before they ever reach the Spring Framework [5][6]. To prevent this and allow Spring to resolve them at runtime, you may need to escape the placeholder (e.g., ${java.io.tmpdir}) [5][7]. - Unresolved Placeholders: If a placeholder cannot be resolved (e.g., the key is missing or mistyped), Spring will typically throw an IllegalArgumentException: Could not resolve placeholder [8]. Ensure the property key is correctly defined in the environment or as a system property accessible to the test context [9][8]. For complex scenarios where you need to resolve properties dynamically based on other beans or environment state, consider using @DynamicPropertySource, which offers more flexibility and higher precedence than @TestPropertySource or the properties attribute [1][3].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show relevant portion of the test file around line ~20
FILE="src/test/java/com/example/bookmark/BookmarkControllerTest.java"
if [ -f "$FILE" ]; then
  echo "=== $FILE (around line 1-60) ==="
  sed -n '1,80p' "$FILE" | nl -ba | sed -n '1,80p'
else
  echo "File not found: $FILE" >&2
fi

# Extract any Spring datasource url properties in this test file
echo "=== grep datasource url in $FILE ==="
rg -n "spring\.datasource\.url|sqlite:" "$FILE" || true

# Check build files for placeholder/resource filtering that might expand ${...}
echo "=== build files search for filtering ==="
ls -la | head
rg -n "resource(s)? filtering|filtering|expand\s*=\s*true|\$\{.*\}" build.gradle* settings.gradle* pom.xml gradle.properties 2>/dev/null || true

# Show Spring Boot version
echo "=== Spring Boot version hints (pom/build.gradle) ==="
rg -n "spring-boot" pom.xml build.gradle* build.gradle.kts 2>/dev/null || true

# If pom exists, show dependency management section lines around spring-boot version
if [ -f pom.xml ]; then
  echo "=== pom.xml spring-boot version lines ==="
  rg -n "spring-boot.*version" pom.xml || true
fi

# List possible build scripts
echo "=== repo root build config files ==="
find . -maxdepth 2 -type f \( -name "pom.xml" -o -name "build.gradle" -o -name "build.gradle.kts" -o -name "settings.gradle" -o -name "settings.gradle.kts" \) -print

Repository: goofmint/bookmark-app-java

Length of output: 221


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show relevant portion of the test file around line ~20
FILE="src/test/java/com/example/bookmark/BookmarkControllerTest.java"
if [ -f "$FILE" ]; then
  echo "=== $FILE (around line 1-80) ==="
  sed -n '1,120p' "$FILE" | nl -ba
else
  echo "File not found: $FILE" >&2
fi

# Extract any Spring datasource url properties in this test file
echo "=== grep datasource url in $FILE ==="
rg -n "spring\.datasource\.url|sqlite:" "$FILE" || true

# Check build files for placeholder/resource filtering that might expand ${...}
echo "=== build files search for resource filtering / ${} expansion ==="
find . -maxdepth 3 -type f \( -name "pom.xml" -o -name "build.gradle" -o -name "build.gradle.kts" -o -name "settings.gradle" -o -name "settings.gradle.kts" -o -name "gradle.properties" \) -print

# Grep only within those files to keep output small
rg -n "filtering|resources\.filtering|expand\s*=\s*true|replace.*\$\{" -S pom.xml build.gradle* build.gradle.kts settings.gradle* settings.gradle.kts gradle.properties 2>/dev/null || true

# Show Spring Boot version
echo "=== Spring Boot version hints ==="
rg -n "spring-boot.*version|springBootVersion|id ['\"]org\.springframework\.boot|spring-boot-starter" pom.xml build.gradle* build.gradle.kts 2>/dev/null || true

Repository: goofmint/bookmark-app-java

Length of output: 221


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="src/test/java/com/example/bookmark/BookmarkControllerTest.java"
if [ -f "$FILE" ]; then
  echo "=== $FILE (first 120 lines) ==="
  sed -n '1,120p' "$FILE" | nl -ba
else
  echo "File not found: $FILE" >&2
fi

echo "=== datasource url/sqlite in $FILE ==="
rg -n "spring\.datasource\.url|sqlite:" "$FILE" || true

echo "=== build config files ==="
find . -maxdepth 3 -type f \( -name "pom.xml" -o -name "build.gradle" -o -name "build.gradle.kts" -o -name "settings.gradle" -o -name "settings.gradle.kts" -o -name "gradle.properties" \) -print

echo "=== resource filtering / placeholder expansion in build configs ==="
rg -n "filtering|resources\.filtering|expand\s*=\s*true|\$\{\w" -S pom.xml build.gradle* build.gradle.kts settings.gradle* settings.gradle.kts gradle.properties 2>/dev/null || true

echo "=== Spring Boot version hints ==="
rg -n "spring-boot.*version|springBootVersion|id ['\"]org\.springframework\.boot|spring-boot-starter" pom.xml build.gradle* build.gradle.kts 2>/dev/null || true

Repository: goofmint/bookmark-app-java

Length of output: 220


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="src/test/java/com/example/bookmark/BookmarkControllerTest.java"
if [ -f "$FILE" ]; then
  echo "=== $FILE (first 120 lines) ==="
  sed -n '1,120p' "$FILE" | nl -ba
else
  echo "File not found: $FILE" >&2
fi

echo "=== datasource url/sqlite in $FILE ==="
rg -n "spring\.datasource\.url|sqlite:" "$FILE" || true

echo "=== build config files ==="
find . -maxdepth 3 -type f \( -name "pom.xml" -o -name "build.gradle" -o -name "build.gradle.kts" -o -name "settings.gradle" -o -name "settings.gradle.kts" -o -name "gradle.properties" \) -print

echo "=== resource filtering / placeholder expansion in build configs ==="
rg -n "filtering|resources\.filtering|expand\s*=\s*true|\$\{\w" -S pom.xml build.gradle* build.gradle.kts settings.gradle* settings.gradle.kts gradle.properties 2>/dev/null || true

echo "=== Spring Boot version hints ==="
rg -n "spring-boot.*version|springBootVersion|id ['\"]org\.springframework\.boot|spring-boot-starter" pom.xml build.gradle* build.gradle.kts 2>/dev/null || true

Repository: goofmint/bookmark-app-java

Length of output: 220


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="src/test/java/com/example/bookmark/BookmarkControllerTest.java"
if [ -f "$FILE" ]; then
  echo "=== $FILE (first 120 lines) ==="
  sed -n '1,120p' "$FILE" | nl -ba
else
  echo "File not found: $FILE" >&2
fi

echo "=== datasource url/sqlite in $FILE ==="
rg -n "spring\.datasource\.url|sqlite:" "$FILE" || true

echo "=== build config files ==="
find . -maxdepth 3 -type f \( -name "pom.xml" -o -name "build.gradle" -o -name "build.gradle.kts" -o -name "settings.gradle" -o -name "settings.gradle.kts" -o -name "gradle.properties" \) -print

echo "=== resource filtering / placeholder expansion in build configs ==="
rg -n "filtering|resources\.filtering|expand\s*=\s*true|\$\{\w" -S pom.xml build.gradle* build.gradle.kts settings.gradle* settings.gradle.kts gradle.properties 2>/dev/null || true

echo "=== Spring Boot version hints ==="
rg -n "spring-boot.*version|springBootVersion|id ['\"]org\.springframework\.boot|spring-boot-starter" pom.xml build.gradle* build.gradle.kts 2>/dev/null || true

Repository: goofmint/bookmark-app-java

Length of output: 220


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="src/test/java/com/example/bookmark/BookmarkControllerTest.java"
if [ -f "$FILE" ]; then
  echo "=== $FILE (first 120 lines) ==="
  sed -n '1,120p' "$FILE" | nl -ba
else
  echo "File not found: $FILE" >&2
fi

echo "=== datasource url/sqlite in $FILE ==="
rg -n "spring\.datasource\.url|sqlite:" "$FILE" || true

echo "=== build config files ==="
find . -maxdepth 3 -type f \( -name "pom.xml" -o -name "build.gradle" -o -name "build.gradle.kts" -o -name "settings.gradle" -o -name "settings.gradle.kts" -o -name "gradle.properties" \) -print

echo "=== resource filtering / placeholder expansion in build configs ==="
rg -n "filtering|resources\.filtering|expand\s*=\s*true|\$\{\w" -S pom.xml build.gradle* build.gradle.kts settings.gradle* settings.gradle.kts gradle.properties 2>/dev/null || true

echo "=== Spring Boot version hints ==="
rg -n "spring-boot.*version|springBootVersion|id ['\"]org\.springframework\.boot|spring-boot-starter" pom.xml build.gradle* build.gradle.kts 2>/dev/null || true

Repository: goofmint/bookmark-app-java

Length of output: 220


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="src/test/java/com/example/bookmark/BookmarkControllerTest.java"

echo "=== test file exists? ==="
ls -la "$FILE" || true

echo "=== $FILE (lines 1-80) ==="
if [ -f "$FILE" ]; then
  sed -n '1,80p' "$FILE"
fi

echo "=== occurrences of sqlite / spring.datasource.url in $FILE ==="
if [ -f "$FILE" ]; then
  rg -n "spring\.datasource\.url|sqlite:" "$FILE" || true
fi

echo "=== repo-wide search for /private/tmp ==="
rg -n "/private/tmp" . || true

echo "=== repo-wide search for spring.datasource.url sqlite ==="
rg -n "spring\.datasource\.url=|sqlite:" . || true

echo "=== build config files ==="
find . -maxdepth 4 -type f \( -name "pom.xml" -o -name "build.gradle" -o -name "build.gradle.kts" -o -name "settings.gradle" -o -name "settings.gradle.kts" -o -name "gradle.properties" \) -print

echo "=== resource filtering / placeholder expansion indicators (pom/build.gradle/etc) ==="
rg -n "resources\.filtering|resource(s)?\.filtering|expand\s*=\s*true|\$\{\w" pom.xml build.gradle* build.gradle.kts settings.gradle* settings.gradle.kts gradle.properties 2>/dev/null || true

echo "=== Spring Boot version hints ==="
rg -n "springBootVersion|spring-boot-starter|org\.springframework\.boot|spring-boot" pom.xml build.gradle* build.gradle.kts 2>/dev/null || true

Repository: goofmint/bookmark-app-java

Length of output: 5305


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== build.gradle (lines 1-160) ==="
sed -n '1,160p' build.gradle

echo "=== any resource filtering / placeholder expansion settings in build.gradle ==="
rg -n "processResources|resources\.filtering|filtering|expand\s*=|replace\s+|\$\{\w" build.gradle || true

echo "=== gradle wrapper / gradle.properties presence ==="
ls -la gradle.properties gradle/wrapper/gradle-wrapper.properties 2>/dev/null || true

Repository: goofmint/bookmark-app-java

Length of output: 1191


テストDBパスの /private/tmp ハードコードを環境非依存に修正してください。BookmarkControllerTest 20行目)

@SpringBootTest(properties=...)jdbc:sqlite:/private/tmp/bookmark-controller-test.db を直接指定しており、Linux等ではディレクトリが存在せずテスト失敗につながり得ます。/private/tmp${java.io.tmpdir} に置き換えるのが安全です(build.gradle に resource filtering 等のビルド時展開設定は見当たりません)。

修正例
-@SpringBootTest(properties = "spring.datasource.url=jdbc:sqlite:/private/tmp/bookmark-controller-test.db")
+@SpringBootTest(properties = "spring.datasource.url=jdbc:sqlite:${java.io.tmpdir}/bookmark-controller-test.db")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/java/com/example/bookmark/BookmarkControllerTest.java` at line 20,
The test hardcodes the SQLite file under /private/tmp in the `@SpringBootTest`
properties on class BookmarkControllerTest; change the annotation so the
datasource URL uses the JVM temp dir placeholder instead (e.g.
jdbc:sqlite:${java.io.tmpdir}/bookmark-controller-test.db) so it becomes
environment-independent; update the `@SpringBootTest`(properties=...) entry on
BookmarkControllerTest to reference ${java.io.tmpdir} instead of /private/tmp
and run tests to verify the DB file lands in the platform temp directory.

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