Skip to main content

Managing Tags and Collections

To manage metadata like tags and collections in this system, you should primarily use the BookmarkService facade. While the BookmarkRepository in app/db/repository.py provides the underlying storage, the service layer handles critical business logic such as validation, cache invalidation, and cascading updates.

Creating and Updating Tags

You can create tags with specific colors and descriptions. The system validates that tag names are not empty and do not exceed 50 characters.

from app.services.bookmark_service import BookmarkService
from app.models.tag import TagColor

service = BookmarkService()

# Create a new tag
tag_data = {
"name": "Research",
"color": "blue",
"description": "Academic papers and articles"
}
tag, error = service.create_tag(tag_data)

if error:
print(f"Failed to create tag: {error}")
else:
print(f"Created tag: {tag.id} ({tag.name})")

# Update an existing tag's color
update_data = {"color": "green"}
updated_tag, error = service.update_tag(tag.id, update_data)

The TagColor enum in app/models/tag.py supports RED, BLUE, GREEN, YELLOW, PURPLE, and GRAY.

Deleting Tags with Cascading Updates

When you delete a tag using BookmarkService.delete_tag, the system automatically performs a cascading update. It identifies every bookmark using that tag, removes the tag reference, saves the updated bookmark, and invalidates the cache for those bookmarks.

# This will delete the tag and strip it from all associated bookmarks
success = service.delete_tag("tag_id_123")

if success:
print("Tag deleted and bookmarks updated.")

This logic is implemented in app/services/bookmark_service.py using the repository's get_bookmarks_with_tag method:

# Internal implementation detail from BookmarkService
for bookmark in self._repo.get_bookmarks_with_tag(tag_id):
bookmark.remove_tag(tag_id)
self._repo.save_bookmark(bookmark)
self._cache.invalidate(bookmark.id)
self._repo.delete_tag(tag_id)

Managing Manual and Smart Collections

Collections allow you to group bookmarks either manually or automatically via filter rules.

Manual Collections

Manual collections require you to explicitly add or remove bookmark IDs.

# Create a manual collection
collection_data = {
"name": "Project Alpha",
"type": "manual"
}
collection, error = service.create_collection(collection_data)

# Add a bookmark to the collection
service.add_to_collection(collection.id, "bookmark_id_456")

Smart Collections

Smart collections use a filter_rule to dynamically determine which bookmarks belong to them.

# Create a smart collection for Python-related bookmarks
smart_data = {
"name": "Python Stuff",
"type": "smart",
"filter_rule": "python"
}
smart_collection, error = service.create_collection(smart_data)

The Collection model in app/models/collection.py includes an internal _apply_filter method that matches the filter_rule against bookmark titles and descriptions.

Retrieving Tagged Bookmarks

To find all bookmarks associated with a specific tag, use the BookmarkRepository directly or via the service if exposed.

from app.db.repository import BookmarkRepository

repo = BookmarkRepository()
# Retrieve all bookmarks that have the 'Research' tag attached
bookmarks = repo.get_bookmarks_with_tag("research_tag_id")

for b in bookmarks:
print(f"Found bookmark: {b.title}")

Troubleshooting and Gotchas

  • In-Memory Storage: The BookmarkRepository is strictly in-memory. All tags and collections will be lost if the application process restarts.
  • Smart Collection Refresh: While the Collection model has _apply_filter logic, the BookmarkService does not automatically re-run these filters during a standard list_collections call. You may need to manually trigger filtering logic if you extend the service.
  • Tag Name Limits: Tag names are limited to 50 characters. Attempting to rename a tag to a longer string via Tag.rename() will raise a ValueError.
  • Manual Collection Constraints: You cannot add a bookmark to a collection if it is already present or if the collection is of type SMART. The Collection.add_bookmark() method will return False in these cases.