1
0
mirror of https://github.com/aquatix/digimarks.git synced 2025-12-06 19:45:12 +01:00

4 Commits

10 changed files with 125 additions and 24 deletions

View File

@@ -30,7 +30,10 @@ necessary packages:
git clone https://github.com/aquatix/digimarks.git
cd digimarks
mkvirtualenv digimarks # or whatever project you are working on
pip install -r requirements.txt
# If you just want to run it, no need for development dependencies
uv sync --active --no-dev
# Otherwise, install everything
uv sync --active
Migrating from version 1

View File

@@ -6,20 +6,19 @@ build-backend = "setuptools.build_meta"
name = "digimarks"
version = "1.1.99"
authors = [
{name = "Michiel Scholten", email = "michiel@diginaut.net"},
{ name = "Michiel Scholten", email = "michiel@diginaut.net" },
]
description='Simple bookmarking service, using a SQLite database to store bookmarks, supporting tags, automatic title fetching and REST API calls.'
description = 'Simple bookmarking service, using a SQLite database to store bookmarks, supporting tags, automatic title fetching and REST API calls.'
readme = "README.rst"
requires-python = ">=3.7"
requires-python = ">=3.10"
keywords = ["bookmarks", "api"]
license = {text = "Apache"}
license = { text = "Apache" }
classifiers = [
"Framework :: FastAPI",
"Programming Language :: Python :: 3",
"License :: OSI Approved :: Apache Software License",
]
dependencies = [
"importlib-metadata; python_version<'3.8'",
"fastapi[all]",
"sqlmodel",
"alembic",
@@ -30,6 +29,29 @@ dependencies = [
"extract_favicon",
"feedgen",
]
[dependency-groups]
dev = [
{include-group = "lint"},
{include-group = "pub"},
{include-group = "test"}
]
test = [
"pytest>=7.0.0",
"pytest-cov>=4.0.0",
]
lint = [
"ruff>=0.1.0",
"mypy>=1.0.0",
]
# Publishing on PyPI
pub = [
"build",
"twine"
]
server = [
"gunicorn>=23.0.0",
]
# dynamic = ["version"]
[project.scripts]

View File

@@ -1,9 +1,15 @@
# Core application
fastapi[all]
sqlmodel
sqlalchemy
pydantic
pydantic_settings
alembic
aiosqlite
# Fetch external resources
httpx
# Fetch title etc from links
beautifulsoup4

View File

@@ -1,9 +1,15 @@
# Core application
fastapi[all]
sqlmodel
sqlalchemy
pydantic
pydantic_settings
alembic
aiosqlite
# Fetch external resources
httpx
# Fetch title etc from links
beautifulsoup4

View File

@@ -1,21 +1,22 @@
"""Bookmark helper functions, like content scrapers, favicon extractor, updater functions."""
import logging
from collections.abc import Sequence
from datetime import UTC, datetime
from typing import Annotated, Sequence
from typing import Annotated
from urllib.parse import urlparse, urlunparse
import bs4
import httpx
import tags_service
import utils
from exceptions import BookmarkNotFound
from extract_favicon import from_html
from fastapi import Query, Request
from models import Bookmark, Visibility
from pydantic import AnyUrl
from sqlmodel import select
from src.digimarks import tags_service, utils
from src.digimarks.exceptions import BookmarkNotFound
from src.digimarks.models import Bookmark, Visibility
DIGIMARKS_USER_AGENT = 'digimarks/2.0.0-dev'
logger = logging.getLogger('digimarks')
@@ -29,7 +30,7 @@ def get_favicon(html_content: str, root_url: str) -> str:
# TODO: save the preferred image to file and return
async def set_information_from_source(logger, bookmark: Bookmark, request: Request) -> Bookmark:
async def set_information_from_source(bookmark: Bookmark, request: Request) -> Bookmark:
"""Request the title by requesting the source url."""
logger.info('Extracting information from url %s', bookmark.url)
try:

View File

@@ -1,16 +1,21 @@
"""digimarks main module."""
import logging
from collections.abc import Sequence
from contextlib import asynccontextmanager
from datetime import UTC, datetime
from typing import Annotated, Sequence, Type
from typing import Annotated
import bookmarks_service
import httpx
import tags_service
from exceptions import BookmarkNotFound
from fastapi import Depends, FastAPI, HTTPException, Query, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from models import DEFAULT_THEME, Bookmark, User, Visibility
from pydantic import DirectoryPath, FilePath
from pydantic_settings import BaseSettings
from sqlalchemy.ext.asyncio import create_async_engine
@@ -18,10 +23,6 @@ from sqlalchemy.orm import sessionmaker
from sqlmodel import desc, select
from sqlmodel.ext.asyncio.session import AsyncSession
from src.digimarks import bookmarks_service, tags_service
from src.digimarks.exceptions import BookmarkNotFound
from src.digimarks.models import DEFAULT_THEME, Bookmark, User, Visibility
DIGIMARKS_VERSION = '2.0.0a1'
@@ -124,7 +125,7 @@ def index(request: Request):
@app.get('/api/v1/admin/{system_key}/users/{user_id}', response_model=User)
async def get_user(session: SessionDep, system_key: str, user_id: int) -> Type[User]:
async def get_user(session: SessionDep, system_key: str, user_id: int) -> type[User]:
"""Show user information."""
logger.info('User %d requested', user_id)
if system_key != settings.system_key:

View File

@@ -22,4 +22,12 @@
.thumbnail img {
/*width: 72px;*/
width: 60px;
}
}
#bookmarkEditForm fieldset {
border: none;
}
#bookmarkEditForm fieldset input, #bookmarkEditForm textarea, #bookmarkEditForm select, #bookmarkEditForm label {
width: 100%;
}

View File

@@ -17,6 +17,7 @@ document.addEventListener('alpine:init', () => {
showTags: Alpine.$persist(false).as('showTags'),
/* Bookmark that is being edited, used to fill the form, etc. */
bookmarkToEdit: Alpine.$persist(null).as('bookmarkToEdit'),
bookmarkToEditError: null,
/* Loading indicator */
loading: false,
@@ -207,12 +208,42 @@ document.addEventListener('alpine:init', () => {
/* Open 'add bookmark' page */
console.log('Start adding bookmark');
this.bookmarkToEdit = {
'url': ''
'url': '',
'title': '',
'note': '',
'tags': ''
}
// this.show_bookmark_details = true;
const editFormDialog = document.getElementById("editFormDialog");
editFormDialog.showModal();
},
async bookmarkURLChanged() {
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/', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
// Bookmark form data
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(data);
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.show_bookmark_details = false;

View File

@@ -1,10 +1,9 @@
"""Helper functions for tags used with Bookmark models."""
from models import Bookmark, Visibility
from sqlalchemy import Sequence
from sqlmodel import select
from src.digimarks.models import Bookmark, Visibility
def i_filter_false(predicate, iterable):
"""Filter an iterable if predicate returns True.

View File

@@ -170,8 +170,32 @@
</span>
</div>
#}
<form method="dialog">
<input type="text" name="">
<form method="dialog" id="bookmarkEditForm">
<fieldset class="form-group">
<label for="bookmark_url">URL</label>
<input id="bookmark_url" type="text" name="bookmark_url" placeholder="url"
x-on:change.debounce="$store.digimarks.bookmarkURLChanged()"
x-model="$store.digimarks.bookmarkToEdit.url">
</fieldset>
<fieldset class="form-group">
<label for="bookmark_title">Title</label>
<input id="bookmark_title" type="text" name="bookmark_title"
placeholder="title (leave empty for autofetch)"
x-model="$store.digimarks.bookmarkToEdit.title">
</fieldset>
<fieldset class="form-group">
<label for="bookmark_note">Note</label>
<textarea id="bookmark_note" type="text" name="bookmark_note"
x-model="$store.digimarks.bookmarkToEdit.note">
</textarea>
</fieldset>
<fieldset class="form-group">
<label for="bookmark_tags">Tags</label>
<input id="bookmark_tags" type="text" name="bookmark_tags"
placeholder="tags, divided bij comma's"
x-model="$store.digimarks.bookmarkToEdit.tags">
</fieldset>
<p x-show="$store.digimarks.bookmarkToEditError" x-data="$store.digimarks.bookmarkToEditError"></p>
<p>
<label>
<input type="checkbox" name="strip" id="strip"/>