mirror of
https://github.com/aquatix/digimarks.git
synced 2025-12-06 23:05:10 +01:00
Compare commits
4 Commits
f05525a9cd
...
651a7e4ece
| Author | SHA1 | Date | |
|---|---|---|---|
| 651a7e4ece | |||
| 63ebc33b04 | |||
| 5f2e2c37fa | |||
| 21306f030e |
@@ -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
|
||||||
|
|||||||
@@ -6,20 +6,19 @@ build-backend = "setuptools.build_meta"
|
|||||||
name = "digimarks"
|
name = "digimarks"
|
||||||
version = "1.1.99"
|
version = "1.1.99"
|
||||||
authors = [
|
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"
|
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 = [
|
||||||
"Framework :: FastAPI",
|
"Framework :: FastAPI",
|
||||||
"Programming Language :: Python :: 3",
|
"Programming Language :: Python :: 3",
|
||||||
"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]
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -22,4 +22,12 @@
|
|||||||
.thumbnail img {
|
.thumbnail img {
|
||||||
/*width: 72px;*/
|
/*width: 72px;*/
|
||||||
width: 60px;
|
width: 60px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#bookmarkEditForm fieldset {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#bookmarkEditForm fieldset input, #bookmarkEditForm textarea, #bookmarkEditForm select, #bookmarkEditForm label {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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"/>
|
||||||
|
|||||||
Reference in New Issue
Block a user