From 3642753266f3d3c68a20191ea3ae636182aba4d4 Mon Sep 17 00:00:00 2001
From: Michiel Scholten
Date: Tue, 6 May 2025 14:44:25 +0200
Subject: [PATCH] Only fetch new bookmarks and tags when cache is not
up-to-date
---
src/digimarks/main.py | 39 ++++++++++++++++--
src/digimarks/static/js/digimarks.js | 54 +++++++++++++++++++------
src/digimarks/templates/user_index.html | 9 ++++-
3 files changed, 86 insertions(+), 16 deletions(-)
diff --git a/src/digimarks/main.py b/src/digimarks/main.py
index a6570e7..40910d3 100644
--- a/src/digimarks/main.py
+++ b/src/digimarks/main.py
@@ -19,7 +19,7 @@ from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from pydantic import AnyUrl, DirectoryPath, FilePath
from pydantic_settings import BaseSettings
-from sqlmodel import AutoString, Field, Session, SQLModel, create_engine, select
+from sqlmodel import AutoString, Field, Session, SQLModel, create_engine, desc, select
DIGIMARKS_USER_AGENT = 'digimarks/2.0.0-dev'
DIGIMARKS_VERSION = '2.0.0a1'
@@ -381,7 +381,11 @@ def get_bookmark(
url_hash: str,
) -> Bookmark:
"""Show bookmark details."""
- bookmark = session.exec(select(Bookmark).where(Bookmark.userkey == user_key, Bookmark.url_hash == url_hash)).first()
+ bookmark = session.exec(
+ select(Bookmark).where(
+ Bookmark.userkey == user_key, Bookmark.url_hash == url_hash, Bookmark.status != Visibility.DELETED
+ )
+ ).first()
# bookmark = session.get(Bookmark, {'url_hash': url_hash, 'userkey': user_key})
return bookmark
@@ -450,13 +454,42 @@ def delete_bookmark(
return {'ok': True}
+@app.get('/api/v1/{user_key}/latest_changes/')
+def bookmarks_changed_since(
+ session: SessionDep,
+ user_key: str,
+):
+ """Last update on server, so the (browser) client knows whether to fetch an update."""
+ latest_modified_bookmark = session.exec(
+ select(Bookmark)
+ .where(Bookmark.userkey == user_key, Bookmark.status != Visibility.DELETED)
+ .order_by(desc(Bookmark.modified_date))
+ ).first()
+ latest_created_bookmark = session.exec(
+ select(Bookmark)
+ .where(Bookmark.userkey == user_key, Bookmark.status != Visibility.DELETED)
+ .order_by(desc(Bookmark.created_date))
+ ).first()
+
+ latest_modification = max(latest_modified_bookmark.modified_date, latest_created_bookmark.created_date)
+
+ return {
+ 'current_time': datetime.now(UTC),
+ 'latest_change': latest_modified_bookmark.modified_date,
+ 'latest_created': latest_created_bookmark.created_date,
+ 'latest_modification': latest_modification,
+ }
+
+
@app.get('/api/v1/{user_key}/tags/')
def list_tags_for_user(
session: SessionDep,
user_key: str,
) -> list[str]:
"""List all tags in use by the user."""
- bookmarks = session.exec(select(Bookmark).where(Bookmark.userkey == user_key)).all()
+ bookmarks = session.exec(
+ select(Bookmark).where(Bookmark.userkey == user_key, Bookmark.status != Visibility.DELETED)
+ ).all()
tags = []
for bookmark in bookmarks:
tags += bookmark.tags_list
diff --git a/src/digimarks/static/js/digimarks.js b/src/digimarks/static/js/digimarks.js
index 9214d6f..93fc6a8 100644
--- a/src/digimarks/static/js/digimarks.js
+++ b/src/digimarks/static/js/digimarks.js
@@ -9,12 +9,18 @@ document.addEventListener('alpine:init', () => {
bookmarks: [],
tags: [],
- tryout: 'hey',
-
+ /* Loading indicator */
loading: false,
+ /* Search filter */
search: '',
+ /* Sort on ~ */
+ sort_title_asc: Alpine.$persist(false).as('sort_title_asc'),
+ sort_title_desc: Alpine.$persist(false).as('sort_title_desc'),
+ sort_created_asc: Alpine.$persist(false).as('sort_created_asc'),
+ sort_created_desc: Alpine.$persist(false).as('sort_created_desc'),
+
async init() {
/** Initialise the application after loading */
// if (this.userKey in this.cache) {
@@ -28,28 +34,47 @@ document.addEventListener('alpine:init', () => {
}, 1000);
},
async loadCache() {
- console.log('Loading bookmarks from cache for user "' + this.userKey + '"');
- this.bookmarks = this.cache[this.userKey]['bookmarks'] || [];
-
+ if (this.userKey in this.cache) {
+ console.log('Loading bookmarks from cache for user "' + this.userKey + '"');
+ this.bookmarks = this.cache[this.userKey]['bookmarks'] || [];
+ }
},
async getBookmarks() {
/** Get the bookmarks from the backend */
this.loading = true;
+ if (!(this.userKey in this.cache)) {
+ /* There is no cache for this userKey yet, create on */
+ console.log('Creating cache for user "' + this.userKey + '"');
+ this.cache[this.userKey] = {'bookmarks': [], 'latest_changes': {}};
+ }
+
+ let latest_status_response = await fetch('/api/v1/' + this.userKey + '/latest_changes/');
+ let latest_status_result = await latest_status_response.json();
+ let should_fetch = false;
+ let latest_modification_in_cache = this.cache[this.userKey].latest_changes.latest_modification || "0000-00-00";
+ should_fetch = latest_status_result.latest_modification > latest_modification_in_cache;
+ this.cache[this.userKey].latest_changes = latest_status_result;
+
+ if (!should_fetch) {
+ console.log('Cache is up-to-date');
+ this.loading = false;
+ return;
+ }
+
console.log('Fetching latest bookmarks from backend for user "' + this.userKey + '"...');
let response = await fetch('/api/v1/' + this.userKey + '/bookmarks/?limit=10000');
let result = await response.json();
- console.log(result);
this.bookmarks = result;
- if (!(this.userKey in this.cache)) {
- /* There is no cache for this userKey yet, create on */
- console.log('caching');
- this.cache[this.userKey] = {'bookmarks': []};
- }
/* Cache the bookmarks to Local Storage */
this.cache[this.userKey]['bookmarks'] = result;
+
+ let tags_response = await fetch('/api/v1/' + this.userKey + '/tags/');
+ let tags_result = await tags_response.json();
+ this.cache[this.userKey]['tags'] = tags_result;
+
this.loading = false;
},
- get filteredItems() {
+ get filteredBookmarks() {
// return this.cache[this.userKey]['bookmarks'].filter(
// i => i.title.includes(this.search)
// )
@@ -58,6 +83,11 @@ document.addEventListener('alpine:init', () => {
i => i.title.match(new RegExp(this.search, "i"))
)
},
+ get filteredTags() {
+ return this.cache[this.userKey].tags.filter(
+ i => i.match(new RegExp(this.search, "i"))
+ )
+ },
async sortAlphabetically(order = 'asc') {
if (order === 'desc') {
this.bookmarks.sort((a, b) => b.title.localeCompare(a.title));
diff --git a/src/digimarks/templates/user_index.html b/src/digimarks/templates/user_index.html
index 7edb870..8c659c8 100644
--- a/src/digimarks/templates/user_index.html
+++ b/src/digimarks/templates/user_index.html
@@ -31,10 +31,17 @@
+
+ tags
+
{% endblock %}