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 @@
+ +