1
0
mirror of https://codeberg.org/diginaut/digimarks.git synced 2026-02-04 20:50:26 +01:00

4 Commits

7 changed files with 85 additions and 28 deletions

2
.gitignore vendored
View File

@@ -77,6 +77,7 @@ celerybeat-schedule
# dotenv # dotenv
.env .env
*.env
# direnv # direnv
.envrc .envrc
@@ -97,6 +98,7 @@ ENV/
# vim # vim
*.swp *.swp
*.swo
# Zed editor # Zed editor
.zed .zed

View File

@@ -30,7 +30,7 @@ necessary packages:
git clone https://codeberg.org/diginaut/digimarks.git git clone https://codeberg.org/diginaut/digimarks.git
cd digimarks cd digimarks
# direnv will now create or activate a virtualenv # direnv will now create or activate a virtualenv
# See https://github.com/direnv/direnv/wiki/Python#uv for direnv uv config # See https://codeberg.org/diginaut/dotfiles/src/branch/master/.config/direnv/direnvrc for direnv uv config
# If you just want to run it, no need for development dependencies # If you just want to run it, no need for development dependencies
uv sync --active --no-dev uv sync --active --no-dev
# Otherwise, install everything in the active virtualenv # Otherwise, install everything in the active virtualenv

View File

@@ -55,7 +55,7 @@ pub = [
"twine" "twine"
] ]
server = [ server = [
"uvicorn", "gunicorn>=23.0.0",
] ]
# dynamic = ["version"] # dynamic = ["version"]

View File

@@ -1,3 +1,3 @@
-r requirements.in -r requirements.in
uvicorn gunicorn

View File

@@ -10,7 +10,8 @@ import bs4
import httpx import httpx
from extract_favicon import from_html from extract_favicon import from_html
from fastapi import Query, Request from fastapi import Query, Request
from pydantic import AnyUrl from fastapi.exceptions import HTTPException
from pydantic import AnyUrl, ValidationError
from sqlmodel import select from sqlmodel import select
from digimarks import tags_service, utils 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.""" """Request the title by requesting the source url."""
logger.info('Extracting information from url %s', bookmark.url) logger.info('Extracting information from url %s', bookmark.url)
try: 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 bookmark.http_status = result.status_code
except httpx.HTTPError as err: except httpx.HTTPError as err:
# For example, "MissingSchema: Invalid URL 'abc': No schema supplied. Perhaps you meant http://abc?" # 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 = '' bookmark.title = ''
return bookmark return bookmark
if bookmark.http_status == 200 or bookmark.http_status == 202: 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: try:
bookmark.title = html.title.text.strip() bookmark.title = html_content.title.text.strip()
except AttributeError: except AttributeError as exc:
bookmark.title = '' 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)) url_parts = urlparse(str(bookmark.url))
root_url = url_parts.scheme + '://' + url_parts.netloc 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)) 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.""" """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: if not bookmark.title:
# Title was empty, automatically fetch it from the url, will also update the status code # 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: if strip_params:
# Strip URL parameters, e.g., tracking params # Strip URL parameters, e.g., tracking params
@@ -129,7 +137,12 @@ async def autocomplete_bookmark(
bookmark.user_key = user_key bookmark.user_key = user_key
# Auto-fill title, fix tags etc. # 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)) url_hash = utils.generate_hash(str(bookmark.url))
result = await session.exec( result = await session.exec(
@@ -157,7 +170,7 @@ async def add_bookmark(
bookmark.user_key = user_key bookmark.user_key = user_key
# Auto-fill title, fix tags etc. # 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)) bookmark.url_hash = utils.generate_hash(str(bookmark.url))
logger.info('Adding bookmark %s for user %s', bookmark.url_hash, user_key) 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) bookmark_db.sqlmodel_update(bookmark_data)
# Autofill title, fix tags, etc. where (still) needed # 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) session.add(bookmark_db)
await session.commit() await session.commit()

View File

@@ -214,10 +214,12 @@ document.addEventListener('alpine:init', () => {
resetEditBookmark() { resetEditBookmark() {
this.bookmarkToEdit = { this.bookmarkToEdit = {
'url_hash': '',
'url': '', 'url': '',
'title': '', 'title': '',
'note': '', 'note': '',
'tags': '' 'tags': '',
'strip_params': false
} }
}, },
async startAddingBookmark() { async startAddingBookmark() {
@@ -233,7 +235,55 @@ document.addEventListener('alpine:init', () => {
console.log('Bookmark URL changed'); console.log('Bookmark URL changed');
// let response = await fetch('/api/v1/' + this.userKey + '/autocomplete_bookmark/'); // let response = await fetch('/api/v1/' + this.userKey + '/autocomplete_bookmark/');
try { 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', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
@@ -249,21 +299,12 @@ document.addEventListener('alpine:init', () => {
const data = await response.json(); const data = await response.json();
// TODO: update form fields if needed (auto-fetched title for example // TODO: update form fields if needed (auto-fetched title for example
console.log(data); console.log(data);
this.bookmarkToEditError = 'lolwut'; // this.bookmarkToEditError = 'lolwut';
} catch (error) { } catch (error) {
// enter your logic for when there is an error (ex. error toast) // enter your logic for when there is an error (ex. error toast)
console.log(error) 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 */
//
} }
}) })
}); });

View File

@@ -212,16 +212,17 @@
x-model="$store.digimarks.bookmarkToEdit.tags"> x-model="$store.digimarks.bookmarkToEdit.tags">
</fieldset> </fieldset>
<p x-show="$store.digimarks.bookmarkToEditError" <p x-show="$store.digimarks.bookmarkToEditError"
x-data="$store.digimarks.bookmarkToEditError"></p> x-text="$store.digimarks.bookmarkToEditError" x-cloak class="error"></p>
<p> <p>
<label> <label>
<input type="checkbox" name="strip" id="strip"/> <input type="checkbox" x-model="$store.digimarks.bookmarkToEdit.strip_params"/>
<span>Strip parameters from url (like <em>?utm_source=social</em> - can break the link!)</span> <span>Strip parameters from url (like <em>?utm_source=social</em> - can break the link!)</span>
</label> </label>
</p> </p>
<div> <div>
<button value="cancel">Cancel</button> <button value="cancel">Cancel</button>
<button @click="$store.digimarks.saveBookmark()">Save</button> <button @click="$store.digimarks.saveBookmark()">Save</button>
<button @click="$store.digimarks.addBookmark()">Add</button>
</div> </div>
</form> </form>
</template> </template>