From f68daf4ac018ef4cb7bfbfbb279a870b453d623e Mon Sep 17 00:00:00 2001
From: Michiel Scholten
Date: Sun, 4 Jan 2026 16:17:48 +0100
Subject: [PATCH] Handle autocompletion, raise appropriate errors
---
src/digimarks/bookmarks_service.py | 35 ++++++++-----
src/digimarks/static/js/digimarks.js | 65 ++++++++++++++++++++-----
src/digimarks/templates/user_index.html | 5 +-
3 files changed, 80 insertions(+), 25 deletions(-)
diff --git a/src/digimarks/bookmarks_service.py b/src/digimarks/bookmarks_service.py
index 21a6533..0d02875 100644
--- a/src/digimarks/bookmarks_service.py
+++ b/src/digimarks/bookmarks_service.py
@@ -10,7 +10,8 @@ import bs4
import httpx
from extract_favicon import from_html
from fastapi import Query, Request
-from pydantic import AnyUrl
+from fastapi.exceptions import HTTPException
+from pydantic import AnyUrl, ValidationError
from sqlmodel import select
from digimarks import tags_service, utils
@@ -34,7 +35,9 @@ async def set_information_from_source(bookmark: Bookmark, request: Request) -> B
"""Request the title by requesting the source url."""
logger.info('Extracting information from url %s', bookmark.url)
try:
- result = await request.app.requests_client.get(bookmark.url, headers={'User-Agent': DIGIMARKS_USER_AGENT})
+ result = await request.app.state.requests_client.get(
+ str(bookmark.url), headers={'User-Agent': DIGIMARKS_USER_AGENT}
+ )
bookmark.http_status = result.status_code
except httpx.HTTPError as err:
# For example, "MissingSchema: Invalid URL 'abc': No schema supplied. Perhaps you meant http://abc?"
@@ -43,11 +46,12 @@ async def set_information_from_source(bookmark: Bookmark, request: Request) -> B
bookmark.title = ''
return bookmark
if bookmark.http_status == 200 or bookmark.http_status == 202:
- html = bs4.BeautifulSoup(result.text, 'html.parser')
+ html_content = bs4.BeautifulSoup(result.text, 'html.parser')
try:
- bookmark.title = html.title.text.strip()
- except AttributeError:
- bookmark.title = ''
+ bookmark.title = html_content.title.text.strip()
+ except AttributeError as exc:
+ logger.error('Error while trying to extract title from URL %s: %s', str(bookmark.url), str(exc))
+ raise HTTPException(status_code=400, detail='Error while trying to extract title')
url_parts = urlparse(str(bookmark.url))
root_url = url_parts.scheme + '://' + url_parts.netloc
@@ -72,11 +76,15 @@ def strip_url_params(url: str) -> str:
return urlunparse((parsed.scheme, parsed.netloc, parsed.path, parsed.params, '', parsed.fragment))
-def update_bookmark_with_info(bookmark: Bookmark, request: Request, strip_params: bool = False):
+async def update_bookmark_with_info(bookmark: Bookmark, request: Request, strip_params: bool = False):
"""Automatically update title, favicon, etc."""
+ if isinstance(bookmark.url, str):
+ # If type of the url is a 'simple' string, ensure it to be an AnyUrl
+ bookmark.url = AnyUrl(bookmark.url)
+
if not bookmark.title:
# Title was empty, automatically fetch it from the url, will also update the status code
- set_information_from_source(bookmark, request)
+ await set_information_from_source(bookmark, request)
if strip_params:
# Strip URL parameters, e.g., tracking params
@@ -129,7 +137,12 @@ async def autocomplete_bookmark(
bookmark.user_key = user_key
# Auto-fill title, fix tags etc.
- update_bookmark_with_info(bookmark, request, strip_params)
+ try:
+ await update_bookmark_with_info(bookmark, request, strip_params)
+ except ValidationError as exc:
+ logger.error('ValidationError while autocompleting bookmark with URL %s', bookmark.url)
+ logger.error('Error was: %s', str(exc))
+ raise HTTPException(status_code=400, detail='Error while autocompleting, likely the URL contained an error')
url_hash = utils.generate_hash(str(bookmark.url))
result = await session.exec(
@@ -157,7 +170,7 @@ async def add_bookmark(
bookmark.user_key = user_key
# Auto-fill title, fix tags etc.
- update_bookmark_with_info(bookmark, request, strip_params)
+ await update_bookmark_with_info(bookmark, request, strip_params)
bookmark.url_hash = utils.generate_hash(str(bookmark.url))
logger.info('Adding bookmark %s for user %s', bookmark.url_hash, user_key)
@@ -193,7 +206,7 @@ async def update_bookmark(
bookmark_db.sqlmodel_update(bookmark_data)
# Autofill title, fix tags, etc. where (still) needed
- update_bookmark_with_info(bookmark, request, strip_params)
+ await update_bookmark_with_info(bookmark, request, strip_params)
session.add(bookmark_db)
await session.commit()
diff --git a/src/digimarks/static/js/digimarks.js b/src/digimarks/static/js/digimarks.js
index 1c7fe36..17866ee 100644
--- a/src/digimarks/static/js/digimarks.js
+++ b/src/digimarks/static/js/digimarks.js
@@ -214,10 +214,12 @@ document.addEventListener('alpine:init', () => {
resetEditBookmark() {
this.bookmarkToEdit = {
+ 'url_hash': '',
'url': '',
'title': '',
'note': '',
- 'tags': ''
+ 'tags': '',
+ 'strip_params': false
}
},
async startAddingBookmark() {
@@ -233,7 +235,55 @@ document.addEventListener('alpine:init', () => {
console.log('Bookmark URL changed');
// let response = await fetch('/api/v1/' + this.userKey + '/autocomplete_bookmark/');
try {
- const response = await fetch('/api/v1/' + this.userKey + '/autocomplete_bookmark/', {
+ const response = await fetch('/api/v1/' + this.userKey + '/autocomplete_bookmark/?strip_params=' + this.bookmarkToEdit.strip_params, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ // Bookmark form data
+ url_hash: this.bookmarkToEdit.url_hash,
+ url: this.bookmarkToEdit.url,
+ title: this.bookmarkToEdit.title,
+ note: this.bookmarkToEdit.note,
+ tags: this.bookmarkToEdit.tags
+ })
+ });
+ const data = await response.json();
+ // TODO: update form fields if needed (auto-fetched title for example
+ console.log('Got response');
+ console.log(response);
+ console.log(data);
+ if (data.ok) {
+ this.bookmarkToEdit.url_hash = data.url_hash;
+ this.bookmarkToEdit.url = data.url;
+ this.bookmarkToEdit.title = data.title;
+ this.bookmarkToEdit.note = data.note;
+ this.bookmarkToEdit.tags = data.tags;
+ } else {
+ console.log('Error occurred');
+ this.bookmarkToEditError = data.detail;
+ }
+ // this.bookmarkToEditError = 'lolwut';
+ } catch (error) {
+ // enter your logic for when there is an error (ex. error toast)
+
+ console.log('error occurred');
+ console.log(error);
+ this.bookmarkToEditError = error.detail;
+ console.log('yesssh?');
+ }
+ },
+ async saveBookmark() {
+ console.log('Saving bookmark');
+ // this.bookmarkToEditVisible = false;
+ // this.show_bookmark_details = false;
+ },
+ async addBookmark() {
+ /* Post new bookmark to the backend */
+ console.log('Adding bookmark');
+ try {
+ const response = await fetch('/api/v1/' + this.userKey + '/add_bookmark/?strip_params=' + this.bookmarkToEdit.strip_params, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
@@ -249,21 +299,12 @@ document.addEventListener('alpine:init', () => {
const data = await response.json();
// TODO: update form fields if needed (auto-fetched title for example
console.log(data);
- this.bookmarkToEditError = 'lolwut';
+ // this.bookmarkToEditError = 'lolwut';
} catch (error) {
// enter your logic for when there is an error (ex. error toast)
console.log(error)
}
- },
- async saveBookmark() {
- console.log('Saving bookmark');
- // this.bookmarkToEditVisible = false;
- // this.show_bookmark_details = false;
- },
- async addBookmark() {
- /* Post new bookmark to the backend */
- //
}
})
});
diff --git a/src/digimarks/templates/user_index.html b/src/digimarks/templates/user_index.html
index 530bc09..d8ce269 100644
--- a/src/digimarks/templates/user_index.html
+++ b/src/digimarks/templates/user_index.html
@@ -212,16 +212,17 @@
x-model="$store.digimarks.bookmarkToEdit.tags">
+ x-text="$store.digimarks.bookmarkToEditError" x-cloak class="error">
+