Skip to content

Add bookmark edit and delete actions#2

Open
goofmint wants to merge 1 commit into
mainfrom
add-bookmark-edit-delete
Open

Add bookmark edit and delete actions#2
goofmint wants to merge 1 commit into
mainfrom
add-bookmark-edit-delete

Conversation

@goofmint
Copy link
Copy Markdown
Owner

@goofmint goofmint commented May 23, 2026

Summary

  • add bookmark edit and delete endpoints
  • add edit/delete controls to bookmark cards
  • render comma-separated tags as individual chips

Tests

  • JAVA_HOME=/opt/homebrew/Cellar/openjdk/26.0.1/libexec/openjdk.jdk/Contents/Home ./gradlew build

Summary by CodeRabbit

リリースノート

  • New Features

    • ブックマークの詳細情報(説明、タグ)を編集できる機能を追加
    • ブックマークを削除できる機能を追加
  • Style

    • ブックマークカードのUIデザインを改善
    • 編集・削除パネルのスタイルを整備
    • モバイル対応レスポンシブレイアウトを強化

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 23, 2026

📝 Walkthrough

概要

BookmarkController に編集・削除の POST エンドポイントを追加し、BookmarkRepository の UPDATE / DELETE 操作、CSS スタイル設定、Thymeleaf テンプレート統合を実装しました。

変更内容

ブックマーク編集・削除機能の実装

レイヤー / ファイル 説明
バックエンド更新・削除操作
src/main/java/com/example/bookmark/BookmarkRepository.java, src/main/java/com/example/bookmark/BookmarkController.java
BookmarkRepositoryupdateDetails(id, description, tags)delete(id) の SQL 実行メソッドを追加。BookmarkController 側で /bookmarks/{id}/edit/bookmarks/{id}/delete の POST ハンドラを実装し、空文字を null に正規化する blankToNull ヘルパーメソッドも追加しました。例外時はエラーフラッシュメッセージを設定してトップへリダイレクトします。
編集・削除フォームのスタイル設定
src/main/resources/static/styles.css
.bookmark-header.bookmark-actions.edit-panel(details marker 非表示含む)、.edit-form.delete-form.icon-button.bookmark-description など、編集・削除 UI 関連のスタイル規則を追加。最大幅 720px 以下では .bookmark-actions を右寄せ(justify-content: end)に調整する応答性設定も含めました。
テンプレート側のフォーム統合
src/main/resources/templates/index.html
ブックマークカード内に折りたたみ編集パネル(details/summary)、タグ・メモ入力フォーム、確認ダイアログ付き削除フォームを追加。タグ表示は #strings.arraySplittrim で空要素を除外して Thymeleaf で描画するよう拡張しました。

🎯 3 (Moderate) | ⏱️ ~20 分

関連する可能性のある PR

  • goofmint/bookmark-app-java#1: このPRが拡張する初期ブックマークアプリコードと同じ BookmarkController および BookmarkRepository クラスを対象に、新しい編集・削除エンドポイントを追加しています。

🐰 詩

リンク集に、編集・削除の機能をば、
詳細整備し、UI整え、
データベースと共に歩みて、
ブックマークの管理、ここに完成す。
~幸せなウサギより~ 🐇✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

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.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed プルリクエストのタイトルは、変更内容の主要な目的を正確に反映しており、ブックマークの編集と削除機能の追加という変更の中心を明確に示しています。
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch add-bookmark-edit-delete

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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/resources/templates/index.html (1)

38-41: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

OGP画像の alt を空文字ではなく意味のある文言にしてください。

この画像は内容理解に寄与するため、alt="" だとスクリーンリーダー利用者に情報が渡りません。ブックマークタイトルを使った代替テキストを推奨します。

差分例
-            <img class="bookmark-image"
+            <img class="bookmark-image"
                  th:if="${bookmark.ogpImageUrl() != null}"
                  th:src="${bookmark.ogpImageUrl()}"
-                 alt="">
+                 th:alt="${bookmark.title()} + ' のサムネイル'">

As per coding guidelines, 「アクセシビリティ: WCAG 2.1 AA に沿って、img の alt 属性...を確認する。」

🤖 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/resources/templates/index.html` around lines 38 - 41, The img tag
uses an empty alt attribute; change it to provide meaningful alt text using the
bookmark title (e.g., replace alt="" with a Thymeleaf attribute like
th:alt="${bookmark.title}" and optionally add a fallback such as
th:alt="${bookmark.title} ?: 'Bookmark image'") so screen readers receive the
bookmark's title when bookmark.ogpImageUrl() is present; keep the existing th:if
and th:src logic.
🧹 Nitpick comments (2)
src/main/resources/static/styles.css (1)

204-206: ⚡ Quick win

アイコン操作に :focus-visible を追加してください。

ホバー時の見た目はありますが、キーボード操作時の視認性を明示する :focus-visible がないため、操作フォーカスが分かりにくくなります。

差分例
 .edit-panel summary:hover,
 .icon-button:hover {
     background: var(--surface-muted);
 }
+
+.edit-panel summary:focus-visible,
+.icon-button:focus-visible {
+    outline: 2px solid var(--accent);
+    outline-offset: 2px;
+}

Also applies to: 232-244

🤖 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/resources/static/styles.css` around lines 204 - 206, The hover-only
focus styles reduce keyboard accessibility: update the CSS rule that targets
".edit-panel summary:hover, .icon-button:hover" to also include the
corresponding ":focus-visible" selectors (e.g. ".edit-panel
summary:focus-visible, .icon-button:focus-visible") so keyboard focus receives
the same visual treatment; repeat the same change for the other similar
hover-only rule later in the file that styles icon/button hover states so all
interactive controls support :focus-visible.
src/main/resources/templates/index.html (1)

47-65: ⚡ Quick win

操作コンテナに適切なセマンティクスを付与してください。

aria-label を付けた div は支援技術で意図どおり解釈されにくいです。role="group" を併用して「編集/削除の操作グループ」であることを明示してください。

差分例
-                    <div class="bookmark-actions" aria-label="ブックマーク操作">
+                    <div class="bookmark-actions" role="group" aria-label="ブックマーク操作">

As per coding guidelines, 「アクセシビリティ: WCAG 2.1 AA に沿って、...aria 属性、キーボード操作可能性を確認する。」

🤖 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/resources/templates/index.html` around lines 47 - 65, The container
div with class "bookmark-actions" currently only uses aria-label which may not
convey grouping semantics to assistive tech; update the element (the div with
class="bookmark-actions") to include role="group" (and keep the existing
aria-label) so the edit-panel <details> and delete-form are announced as a
single operation group, ensuring keyboard/ARIA semantics are explicit for this
"編集/削除の操作グループ".
🤖 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/main/java/com/example/bookmark/BookmarkController.java`:
- Around line 56-60: Add server-side validation in the controller methods (at
least in edit and the other endpoints noted) so you validate input before
calling the repository: check `@PathVariable` id is > 0, enforce max-length limits
for description and tags (define constants like DESCRIPTION_MAX and TAGS_MAX
that match the client-side maxlength), and if validation fails throw a
ResponseStatusException(HttpStatus.BAD_REQUEST, "<field> invalid") or return an
appropriate 400 response; ensure these checks occur at the start of edit(...)
and the other methods referenced (lines around 71-73 and 81-86) and that
repository methods are only called when validation passes.

In `@src/main/java/com/example/bookmark/BookmarkRepository.java`:
- Around line 40-53: The updateDetails and delete methods currently discard
jdbcTemplate.update(...) return values so callers can't detect "0 rows updated";
change both methods (updateDetails and delete in BookmarkRepository) to return
int and return the result of jdbcTemplate.update(...), e.g. int rows =
jdbcTemplate.update(...); return rows; then update controller call sites to
check for rows == 0 and handle as an error/redirect accordingly so missing-id
updates/deletes are surfaced to the user.

---

Outside diff comments:
In `@src/main/resources/templates/index.html`:
- Around line 38-41: The img tag uses an empty alt attribute; change it to
provide meaningful alt text using the bookmark title (e.g., replace alt="" with
a Thymeleaf attribute like th:alt="${bookmark.title}" and optionally add a
fallback such as th:alt="${bookmark.title} ?: 'Bookmark image'") so screen
readers receive the bookmark's title when bookmark.ogpImageUrl() is present;
keep the existing th:if and th:src logic.

---

Nitpick comments:
In `@src/main/resources/static/styles.css`:
- Around line 204-206: The hover-only focus styles reduce keyboard
accessibility: update the CSS rule that targets ".edit-panel summary:hover,
.icon-button:hover" to also include the corresponding ":focus-visible" selectors
(e.g. ".edit-panel summary:focus-visible, .icon-button:focus-visible") so
keyboard focus receives the same visual treatment; repeat the same change for
the other similar hover-only rule later in the file that styles icon/button
hover states so all interactive controls support :focus-visible.

In `@src/main/resources/templates/index.html`:
- Around line 47-65: The container div with class "bookmark-actions" currently
only uses aria-label which may not convey grouping semantics to assistive tech;
update the element (the div with class="bookmark-actions") to include
role="group" (and keep the existing aria-label) so the edit-panel <details> and
delete-form are announced as a single operation group, ensuring keyboard/ARIA
semantics are explicit for this "編集/削除の操作グループ".
🪄 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: d5b6a17a-85c1-4766-b4cb-9a5058a67cdf

📥 Commits

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

📒 Files selected for processing (4)
  • 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/index.html

Comment on lines +56 to +60
@PostMapping("/bookmarks/{id}/edit")
public String edit(
@PathVariable long id,
@RequestParam(required = false) String description,
@RequestParam(required = false) String tags,
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

編集・削除エンドポイントにサーバー側バリデーションを追加してください。

id の正値チェックと、description/tags の長さチェックが未実装です。クライアント側 maxlength だけでは回避可能なので、Controller で必ず検証してから Repository を呼んでください。

差分例
+    private static final int MAX_TAGS_LENGTH = 500;
+    private static final int MAX_DESCRIPTION_LENGTH = 2000;
+
     `@PostMapping`("/bookmarks/{id}/edit")
     public String edit(
             `@PathVariable` long id,
             `@RequestParam`(required = false) String description,
             `@RequestParam`(required = false) String tags,
             RedirectAttributes redirectAttributes
     ) {
+        if (id <= 0) {
+            redirectAttributes.addFlashAttribute("error", "不正なIDです。");
+            return "redirect:/";
+        }
+        String normalizedDescription = blankToNull(description);
+        String normalizedTags = blankToNull(tags);
+        if (normalizedDescription != null && normalizedDescription.length() > MAX_DESCRIPTION_LENGTH) {
+            redirectAttributes.addFlashAttribute("error", "メモが長すぎます。");
+            return "redirect:/";
+        }
+        if (normalizedTags != null && normalizedTags.length() > MAX_TAGS_LENGTH) {
+            redirectAttributes.addFlashAttribute("error", "タグが長すぎます。");
+            return "redirect:/";
+        }
         try {
-            bookmarkRepository.updateDetails(id, blankToNull(description), blankToNull(tags));
+            bookmarkRepository.updateDetails(id, normalizedDescription, normalizedTags);
         } catch (Exception ex) {
             redirectAttributes.addFlashAttribute("error", "ブックマークを更新できませんでした。");
         }
         return "redirect:/";
     }

     `@PostMapping`("/bookmarks/{id}/delete")
     public String delete(`@PathVariable` long id, RedirectAttributes redirectAttributes) {
+        if (id <= 0) {
+            redirectAttributes.addFlashAttribute("error", "不正なIDです。");
+            return "redirect:/";
+        }
         try {
             bookmarkRepository.delete(id);

As per coding guidelines, 「入力検証: @RequestParam 等の外部入力に対するバリデーション(必須・形式・長さ)が網羅されているか確認する。」

Also applies to: 71-73, 81-86

🤖 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 56 -
60, Add server-side validation in the controller methods (at least in edit and
the other endpoints noted) so you validate input before calling the repository:
check `@PathVariable` id is > 0, enforce max-length limits for description and
tags (define constants like DESCRIPTION_MAX and TAGS_MAX that match the
client-side maxlength), and if validation fails throw a
ResponseStatusException(HttpStatus.BAD_REQUEST, "<field> invalid") or return an
appropriate 400 response; ensure these checks occur at the start of edit(...)
and the other methods referenced (lines around 71-73 and 81-86) and that
repository methods are only called when validation passes.

Comment on lines +40 to +53
public void updateDetails(long id, String description, String tags) {
jdbcTemplate.update("""
UPDATE bookmarks
SET description = ?, tags = ?, updated_at = datetime('now')
WHERE id = ?
""", description, tags, id);
}

public void delete(long id) {
jdbcTemplate.update("""
DELETE FROM bookmarks
WHERE id = ?
""", id);
}
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 | 🟡 Minor | ⚡ Quick win

更新・削除の「0件更新」を検知できる形にしてください。

jdbcTemplate.update(...) の戻り値(更新件数)を捨てているため、存在しない id でも成功扱いになります。int を返して Controller 側で 0 件時にエラー表示へ分岐できるようにすると、失敗時の挙動が一貫します。

差分例
-    public void updateDetails(long id, String description, String tags) {
-        jdbcTemplate.update("""
+    public int updateDetails(long id, String description, String tags) {
+        return jdbcTemplate.update("""
                 UPDATE bookmarks
                 SET description = ?, tags = ?, updated_at = datetime('now')
                 WHERE id = ?
                 """, description, tags, id);
     }

-    public void delete(long id) {
-        jdbcTemplate.update("""
+    public int delete(long id) {
+        return jdbcTemplate.update("""
                 DELETE FROM bookmarks
                 WHERE id = ?
                 """, id);
     }

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/BookmarkRepository.java` around lines 40 -
53, The updateDetails and delete methods currently discard
jdbcTemplate.update(...) return values so callers can't detect "0 rows updated";
change both methods (updateDetails and delete in BookmarkRepository) to return
int and return the result of jdbcTemplate.update(...), e.g. int rows =
jdbcTemplate.update(...); return rows; then update controller call sites to
check for rows == 0 and handle as an error/redirect accordingly so missing-id
updates/deletes are surfaced to the user.

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