Skip to content

feat: implement link update and delete functionality with correspondi…#456

Merged
robfrank merged 2 commits intomainfrom
feat/links-domain
Nov 30, 2025
Merged

feat: implement link update and delete functionality with correspondi…#456
robfrank merged 2 commits intomainfrom
feat/links-domain

Conversation

@robfrank
Copy link
Owner

This pull request adds full support for editing and deleting links in the application, covering both backend and frontend components. The backend introduces new use cases and services for updating and deleting links, with proper ownership checks, while the frontend provides new dialogs and actions for users to edit or delete links directly from the link list. The API layer is also extended to support these new operations.

Backend: Link management services and API integration

  • Added UpdateLinkUseCase and DeleteLinkUseCase interfaces, with corresponding implementations (UpdateLinkService, DeleteLinkService) that verify link ownership before allowing updates or deletions. [1] [2] [3] [4] [5]
  • Introduced LinkController to handle PATCH (edit) and DELETE requests for links, returning updated link data or appropriate status codes.
  • Registered the new controller and endpoints in the application startup and web builder, including authentication and permission checks. [1] [2] [3]

Frontend: Edit and delete link UI

  • Added edit and delete icon buttons to each link in LinkList, opening dialogs for editing link details or confirming deletion.
  • Implemented dialog components for editing a link's title/description and confirming link deletion, with immediate UI updates after actions. [1] [2] [3]

API client enhancements

  • Extended the API service with updateLink and deleteLink methods to interact with the new backend endpoints.

Documentation

  • Updated the roadmap to mark the edit and delete link features as implemented for both backend and frontend.

@gemini-code-assist
Copy link

Summary of Changes

Hello @robfrank, 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!

This pull request delivers full support for managing user links, allowing them to be updated and deleted. The changes span the entire application stack, from new backend services that enforce ownership and handle persistence, to new API endpoints, and a user-friendly frontend interface with interactive dialogs for these operations. This enhancement significantly improves the user's ability to control their saved links.

Highlights

  • Full Link Management: Implemented comprehensive functionality for updating and deleting links across the application.
  • Backend Logic: Introduced 'UpdateLinkUseCase' and 'DeleteLinkUseCase' with corresponding services, ensuring proper link ownership checks.
  • API Endpoints: Added new PATCH and DELETE endpoints for links in 'LinkController', integrated with authentication and permission handling.
  • Frontend UI: Enhanced 'LinkList' with edit and delete icon buttons, along with dedicated dialogs for editing link details and confirming deletion, providing immediate UI feedback.
  • API Client Update: Extended the frontend API service with 'updateLink' and 'deleteLink' methods to interact with the new backend endpoints.
  • Roadmap Progress: Updated the project roadmap to reflect the completion of these link management features.
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 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 counter productive. 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.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

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.

@mergify
Copy link
Contributor

mergify bot commented Nov 30, 2025

🧪 CI Insights

Here's what we observed from your CI run for 36ff212.

🟢 All jobs passed!

But CI Insights is watching 👀

Copy link

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

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 successfully implements the update and delete functionality for links, including both backend services and frontend UI components. The changes are well-structured and cover the necessary use cases. I've identified a critical issue in the web layer's permission handling, a high-severity bug in the persistence layer that could lead to data loss, and a few medium-severity issues related to API consistency and frontend performance. Addressing these points will significantly improve the robustness and efficiency of the new features.

Comment on lines +118 to +130
public WebBuilder withLinkManagementController(LinkController linkController) {
app.before("/api/v1/links/{id}", requireAuthentication);

// Update link
app.before("/api/v1/links/{id}", RequirePermission.any(authorizationService, Role.Permissions.UPDATE_OWN_LINKS));
app.patch("/api/v1/links/{id}", linkController::updateLink);

// Delete link
app.before("/api/v1/links/{id}", RequirePermission.any(authorizationService, Role.Permissions.DELETE_OWN_LINKS));
app.delete("/api/v1/links/{id}", linkController::deleteLink);

return this;
}

Choose a reason for hiding this comment

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

critical

The permission checks for updating and deleting links are incorrectly configured. Javalin's app.before(path, handler) applies the handler to all HTTP methods for that path. As a result, a PATCH request to /api/v1/links/{id} will trigger both the UPDATE_OWN_LINKS and DELETE_OWN_LINKS permission checks. This means a user who can update but not delete a link will have their PATCH request denied. The same issue exists for DELETE requests.

The permission checks should be specific to the HTTP method.

  public WebBuilder withLinkManagementController(LinkController linkController) {
    app.before("/api/v1/links/{id}", requireAuthentication);

    app.before("/api/v1/links/{id}", ctx -> {
      switch (ctx.method()) {
        case PATCH -> RequirePermission.any(authorizationService, Role.Permissions.UPDATE_OWN_LINKS).handle(ctx);
        case DELETE -> RequirePermission.any(authorizationService, Role.Permissions.DELETE_OWN_LINKS).handle(ctx);
        default -> {
          // No-op for other methods
        }
      }
    });

    app.patch("/api/v1/links/{id}", linkController::updateLink);
    app.delete("/api/v1/links/{id}", linkController::deleteLink);

    return this;
  }

Comment on lines +64 to +97
public Link updateLink(Link link) {
try {
database.transaction(() -> {
database.command(
"sql",
"""
UPDATE Link SET
url = ?,
title = ?,
description = ?,
extractedAt = ?,
contentType = ?,
fullText = ?,
summary = ?,
imageUrl = ?
WHERE id = ?
""",
link.url(),
link.title(),
link.description(),
link.extractedAt().truncatedTo(ChronoUnit.SECONDS).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")),
link.contentType(),
null, // fullText not in Link record yet? Wait, Link record doesn't have
// fullText/summary/imageUrl.
null, // But saveLink uses them?
null, // Let's check saveLink again.
link.id()
);
});
return link;
} catch (ArcadeDBException e) {
throw new DatabaseException("Failed to update link: " + link.id(), e);
}
}

Choose a reason for hiding this comment

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

high

The updateLink method has a few issues:

  • It updates fields like url, extractedAt, and contentType. Based on the UpdateLinkService, only title and description should be mutable. Updating a link's URL is particularly risky.
  • It explicitly sets fullText, summary, and imageUrl to null, which will erase any existing data in these columns. The comments in the code suggest there's some confusion about the Link record and the database schema.

A better approach is to only update the fields that are meant to be changed.

  public Link updateLink(Link link) {
    try {
      database.transaction(() -> {
        database.command(
          "sql",
          """
          UPDATE Link SET
          title = ?,
          description = ?
          WHERE id = ?
          """,
          link.title(),
          link.description(),
          link.id()
        );
      });
      return link;
    } catch (ArcadeDBException e) {
      throw new DatabaseException("Failed to update link: " + link.id(), e);
    }
  }

Comment on lines +149 to +160
const handleUpdateLink = async () => {
if (!linkToEdit) return;
try {
await api.updateLink(linkToEdit.id, { title: editTitle, description: editDescription });
setEditLinkDialogOpen(false);
setLinkToEdit(null);
fetchLinks();
} catch (err) {
console.error("Error updating link:", err);
setError("Failed to update link.");
}
};

Choose a reason for hiding this comment

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

medium

After successfully updating a link, you are re-fetching the entire list of links with fetchLinks(). This is inefficient and can be slow if the list is large. The api.updateLink method returns the updated link object. You can use this object to update the link in the local state directly, providing a faster and smoother user experience.

Suggested change
const handleUpdateLink = async () => {
if (!linkToEdit) return;
try {
await api.updateLink(linkToEdit.id, { title: editTitle, description: editDescription });
setEditLinkDialogOpen(false);
setLinkToEdit(null);
fetchLinks();
} catch (err) {
console.error("Error updating link:", err);
setError("Failed to update link.");
}
};
const handleUpdateLink = async () => {
if (!linkToEdit) return;
try {
const updatedLink = await api.updateLink(linkToEdit.id, { title: editTitle, description: editDescription });
setLinks((prevLinks) => prevLinks.map((link) => (link.id === updatedLink.id ? updatedLink : link)));
setEditLinkDialogOpen(false);
setLinkToEdit(null);
} catch (err) {
console.error("Error updating link:", err);
setError("Failed to update link.");
}
};

Comment on lines +167 to +178
const handleConfirmDelete = async () => {
if (!linkToDelete) return;
try {
await api.deleteLink(linkToDelete.id);
setDeleteLinkDialogOpen(false);
setLinkToDelete(null);
fetchLinks();
} catch (err) {
console.error("Error deleting link:", err);
setError("Failed to delete link.");
}
};

Choose a reason for hiding this comment

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

medium

Similar to the update operation, after deleting a link, you re-fetch the entire list. This can be optimized by removing the deleted link from the local state directly.

Suggested change
const handleConfirmDelete = async () => {
if (!linkToDelete) return;
try {
await api.deleteLink(linkToDelete.id);
setDeleteLinkDialogOpen(false);
setLinkToDelete(null);
fetchLinks();
} catch (err) {
console.error("Error deleting link:", err);
setError("Failed to delete link.");
}
};
const handleConfirmDelete = async () => {
if (!linkToDelete) return;
try {
await api.deleteLink(linkToDelete.id);
setLinks((prevLinks) => prevLinks.filter((link) => link.id !== linkToDelete.id));
setDeleteLinkDialogOpen(false);
setLinkToDelete(null);
} catch (err) {
console.error("Error deleting link:", err);
setError("Failed to delete link.");
}
};

@codacy-production
Copy link

codacy-production bot commented Nov 30, 2025

Coverage summary from Codacy

See diff coverage on Codacy

Coverage variation Diff coverage
-1.35% (target: -1.00%) 0.00%
Coverage variation details
Coverable lines Covered lines Coverage
Common ancestor commit (515009d) 2287 1188 51.95%
Head commit (36ff212) 2348 (+61) 1188 (+0) 50.60% (-1.35%)

Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch: <coverage of head commit> - <coverage of common ancestor commit>

Diff coverage details
Coverable lines Covered lines Diff coverage
Pull request (#456) 61 0 0.00%

Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified: <covered lines added or modified>/<coverable lines added or modified> * 100%

See your quality gate settings    Change summary preferences

@robfrank robfrank merged commit f2025da into main Nov 30, 2025
9 of 10 checks passed
@robfrank robfrank deleted the feat/links-domain branch November 30, 2025 22:22
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