mirror of
https://codeberg.org/diginaut/digimarks.git
synced 2026-02-04 18:30:26 +01:00
Compare commits
4 Commits
b4aff120c8
...
f68daf4ac0
| Author | SHA1 | Date | |
|---|---|---|---|
| f68daf4ac0 | |||
| be34c6e88f | |||
| 47a0f31ec3 | |||
| 05fa94ef41 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ pub = [
|
|||||||
"twine"
|
"twine"
|
||||||
]
|
]
|
||||||
server = [
|
server = [
|
||||||
"uvicorn",
|
"gunicorn>=23.0.0",
|
||||||
]
|
]
|
||||||
# dynamic = ["version"]
|
# dynamic = ["version"]
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
-r requirements.in
|
-r requirements.in
|
||||||
|
|
||||||
uvicorn
|
gunicorn
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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 */
|
|
||||||
//
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user