Skip to main content

Managing Bookmark Lifecycle

Manage the lifecycle of bookmarks—including creation, partial updates, and state transitions like archiving or trashing—using the BookmarkService facade.

Creating a New Bookmark

To create a bookmark, use the create_bookmark method. This method validates the input data, persists the entity to the repository, indexes it for search, and invalidates any relevant cache entries.

from app.services.bookmark_service import BookmarkService

service = BookmarkService()

data = {
"url": "https://example.com",
"title": "Example Domain",
"description": "A domain used for illustrative examples"
}

bookmark, error = service.create_bookmark(data)

if error:
# Handle validation errors (e.g., invalid URL or empty title)
print(f"Failed to create bookmark: {error}")
else:
print(f"Created bookmark with ID: {bookmark.id}")

The create_bookmark method returns a tuple of (Optional[Bookmark], Optional[str]). If validation fails (via internal helpers like _validate_url), the first element is None and the second contains the error message.

Updating Bookmark Metadata

The update_bookmark method performs partial updates. It only modifies the fields provided in the data dictionary and automatically updates the updated_at timestamp via the _touch() method.

update_data = {
"title": "Updated Example Title",
"description": "An updated description for this bookmark."
}

bookmark, error = service.update_bookmark("bookmark_id_123", update_data)

if error:
print(f"Update failed: {error}")
elif not bookmark:
print("Bookmark not found")
else:
print(f"Successfully updated: {bookmark.title}")

Transitioning Bookmark States

Bookmarks in this system follow a specific lifecycle defined by the BookmarkStatus enum: ACTIVE, ARCHIVED, and TRASHED. The BookmarkService provides dedicated methods for these transitions.

Archiving a Bookmark

Archiving moves a bookmark out of the primary view without deleting it.

archived_bookmark = service.archive_bookmark("bookmark_id_123")
if archived_bookmark:
print(f"Status is now: {archived_bookmark.status.value}") # "archived"

Soft-Deleting (Trashing) a Bookmark

The delete_bookmark method performs a soft-delete. It does not remove the record from the database; instead, it transitions the status to TRASHED.

success = service.delete_bookmark("bookmark_id_123")
if not success:
print("Bookmark not found")

Restoring a Bookmark

You can restore a bookmark from either the ARCHIVED or TRASHED state back to ACTIVE.

restored_bookmark = service.restore_bookmark("bookmark_id_123")
if restored_bookmark:
print(f"Status restored to: {restored_bookmark.status.value}") # "active"

Listing Bookmarks by Status

When retrieving bookmarks, you can filter the results by their lifecycle state using the status parameter in list_bookmarks.

# Fetch only archived bookmarks
archived_list, total = service.list_bookmarks(page=1, per_page=10, status="archived")

# Fetch only trashed bookmarks
trash_list, total = service.list_bookmarks(page=1, per_page=10, status="trashed")

# Fetch active bookmarks (default behavior if status is not provided depends on repository implementation)
active_list, total = service.list_bookmarks(page=1, per_page=25, status="active")

Implementation Details

Singleton Pattern

BookmarkService is implemented as a singleton. You should instantiate it directly in your modules; it will share the same BookmarkRepository, LRUCache, and SearchIndex instances across the entire application.

# In app/routes/bookmarks.py
_service = BookmarkService()

Validation and Errors

Validation is handled within the service layer before any persistence occurs. Methods that modify data return a (result, error) tuple. Always check the error string to handle cases like:

  • Invalid URL formats.
  • Missing or empty titles.
  • Invalid tag names (when managing tags through the service).

Cache Invalidation

The service automatically manages the LRUCache. Any operation that modifies a bookmark (update_bookmark, archive_bookmark, delete_bookmark, etc.) calls self._cache.invalidate(bookmark_id) to ensure subsequent retrievals fetch the fresh state from the repository.