1
0
mirror of https://github.com/aquatix/digimarks.git synced 2025-12-07 00:15:10 +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 git clone https://github.com/aquatix/digimarks.git
cd digimarks cd digimarks
mkvirtualenv digimarks # or whatever project you are working on 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 Migrating from version 1

View File

@@ -10,7 +10,7 @@ authors = [
] ]
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" readme = "README.rst"
requires-python = ">=3.7" requires-python = ">=3.10"
keywords = ["bookmarks", "api"] keywords = ["bookmarks", "api"]
license = { text = "Apache" } license = { text = "Apache" }
classifiers = [ classifiers = [
@@ -19,7 +19,6 @@ classifiers = [
"License :: OSI Approved :: Apache Software License", "License :: OSI Approved :: Apache Software License",
] ]
dependencies = [ dependencies = [
"importlib-metadata; python_version<'3.8'",
"fastapi[all]", "fastapi[all]",
"sqlmodel", "sqlmodel",
"alembic", "alembic",
@@ -30,6 +29,29 @@ dependencies = [
"extract_favicon", "extract_favicon",
"feedgen", "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"] # dynamic = ["version"]
[project.scripts] [project.scripts]

View File

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

View File

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

View File

@@ -1,21 +1,22 @@
"""Bookmark helper functions, like content scrapers, favicon extractor, updater functions.""" """Bookmark helper functions, like content scrapers, favicon extractor, updater functions."""
import logging import logging
from collections.abc import Sequence
from datetime import UTC, datetime from datetime import UTC, datetime
from typing import Annotated, Sequence from typing import Annotated
from urllib.parse import urlparse, urlunparse from urllib.parse import urlparse, urlunparse
import bs4 import bs4
import httpx import httpx
import tags_service
import utils
from exceptions import BookmarkNotFound
from extract_favicon import from_html from extract_favicon import from_html
from fastapi import Query, Request from fastapi import Query, Request
from models import Bookmark, Visibility
from pydantic import AnyUrl from pydantic import AnyUrl
from sqlmodel import select 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' DIGIMARKS_USER_AGENT = 'digimarks/2.0.0-dev'
logger = logging.getLogger('digimarks') 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 # 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.""" """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:

View File

@@ -1,16 +1,21 @@
"""digimarks main module.""" """digimarks main module."""
import logging import logging
from collections.abc import Sequence
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from datetime import UTC, datetime from datetime import UTC, datetime
from typing import Annotated, Sequence, Type from typing import Annotated
import bookmarks_service
import httpx import httpx
import tags_service
from exceptions import BookmarkNotFound
from fastapi import Depends, FastAPI, HTTPException, Query, Request from fastapi import Depends, FastAPI, HTTPException, Query, Request
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import HTMLResponse from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from models import DEFAULT_THEME, Bookmark, User, Visibility
from pydantic import DirectoryPath, FilePath from pydantic import DirectoryPath, FilePath
from pydantic_settings import BaseSettings from pydantic_settings import BaseSettings
from sqlalchemy.ext.asyncio import create_async_engine from sqlalchemy.ext.asyncio import create_async_engine
@@ -18,10 +23,6 @@ from sqlalchemy.orm import sessionmaker
from sqlmodel import desc, select from sqlmodel import desc, select
from sqlmodel.ext.asyncio.session import AsyncSession 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' 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) @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.""" """Show user information."""
logger.info('User %d requested', user_id) logger.info('User %d requested', user_id)
if system_key != settings.system_key: if system_key != settings.system_key:

View File

@@ -23,3 +23,11 @@
/*width: 72px;*/ /*width: 72px;*/
width: 60px; 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'), showTags: Alpine.$persist(false).as('showTags'),
/* Bookmark that is being edited, used to fill the form, etc. */ /* Bookmark that is being edited, used to fill the form, etc. */
bookmarkToEdit: Alpine.$persist(null).as('bookmarkToEdit'), bookmarkToEdit: Alpine.$persist(null).as('bookmarkToEdit'),
bookmarkToEditError: null,
/* Loading indicator */ /* Loading indicator */
loading: false, loading: false,
@@ -207,12 +208,42 @@ document.addEventListener('alpine:init', () => {
/* Open 'add bookmark' page */ /* Open 'add bookmark' page */
console.log('Start adding bookmark'); console.log('Start adding bookmark');
this.bookmarkToEdit = { this.bookmarkToEdit = {
'url': '' 'url': '',
'title': '',
'note': '',
'tags': ''
} }
// this.show_bookmark_details = true; // this.show_bookmark_details = true;
const editFormDialog = document.getElementById("editFormDialog"); const editFormDialog = document.getElementById("editFormDialog");
editFormDialog.showModal(); 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() { async saveBookmark() {
console.log('Saving bookmark'); console.log('Saving bookmark');
// this.show_bookmark_details = false; // this.show_bookmark_details = false;

View File

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

View File

@@ -170,8 +170,32 @@
</span> </span>
</div> </div>
#} #}
<form method="dialog"> <form method="dialog" id="bookmarkEditForm">
<input type="text" name=""> <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> <p>
<label> <label>
<input type="checkbox" name="strip" id="strip"/> <input type="checkbox" name="strip" id="strip"/>