mirror of
https://codeberg.org/diginaut/digimarks.git
synced 2026-02-04 15:00:26 +01:00
Compare commits
22 Commits
8810a47faa
...
fastapi
| Author | SHA1 | Date | |
|---|---|---|---|
| dae6c5da18 | |||
| f68daf4ac0 | |||
| be34c6e88f | |||
| 47a0f31ec3 | |||
| 05fa94ef41 | |||
| b4aff120c8 | |||
| 82e4202482 | |||
| 9b03d51276 | |||
| fe734d6dd8 | |||
| 2936a4815a | |||
| 09c685f2aa | |||
| 0b08f0fa81 | |||
| 77dd621280 | |||
| a9f8236ee6 | |||
| ac9e010808 | |||
| 21f5f34e4f | |||
| 971ede6067 | |||
| 96a8946a9a | |||
| 14f09a2dfb | |||
| 9d813b7ea6 | |||
| 79be98abea | |||
| a7498a2fba |
1
.envrc.example
Normal file
1
.envrc.example
Normal file
@@ -0,0 +1 @@
|
|||||||
|
layout uv
|
||||||
9
.gitignore
vendored
9
.gitignore
vendored
@@ -77,10 +77,15 @@ celerybeat-schedule
|
|||||||
|
|
||||||
# dotenv
|
# dotenv
|
||||||
.env
|
.env
|
||||||
|
*.env
|
||||||
|
|
||||||
|
# direnv
|
||||||
|
.envrc
|
||||||
|
|
||||||
# virtualenv
|
# virtualenv
|
||||||
venv/
|
venv/
|
||||||
ENV/
|
ENV/
|
||||||
|
.venv
|
||||||
|
|
||||||
# Spyder project settings
|
# Spyder project settings
|
||||||
.spyderproject
|
.spyderproject
|
||||||
@@ -93,6 +98,10 @@ ENV/
|
|||||||
|
|
||||||
# vim
|
# vim
|
||||||
*.swp
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# Zed editor
|
||||||
|
.zed
|
||||||
|
|
||||||
# digimarks
|
# digimarks
|
||||||
static/favicons
|
static/favicons
|
||||||
|
|||||||
21
README.rst
21
README.rst
@@ -27,9 +27,10 @@ necessary packages:
|
|||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
git clone https://github.com/aquatix/digimarks.git
|
git clone https://codeberg.org/diginaut/digimarks.git
|
||||||
cd digimarks
|
cd digimarks
|
||||||
mkvirtualenv digimarks # or whatever project you are working on
|
# direnv will now create or activate a virtualenv
|
||||||
|
# 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
|
||||||
@@ -124,7 +125,7 @@ Attributions
|
|||||||
'M' favicon by `Freepik`_.
|
'M' favicon by `Freepik`_.
|
||||||
|
|
||||||
|
|
||||||
.. _digimarks: https://github.com/aquatix/digimarks
|
.. _digimarks: https://codeberg.org/diginaut/digimarks
|
||||||
.. |PyPI version| image:: https://img.shields.io/pypi/v/digimarks.svg
|
.. |PyPI version| image:: https://img.shields.io/pypi/v/digimarks.svg
|
||||||
:target: https://pypi.python.org/pypi/digimarks/
|
:target: https://pypi.python.org/pypi/digimarks/
|
||||||
.. |PyPI license| image:: https://img.shields.io/github/license/aquatix/digimarks.svg
|
.. |PyPI license| image:: https://img.shields.io/github/license/aquatix/digimarks.svg
|
||||||
@@ -135,11 +136,11 @@ Attributions
|
|||||||
.. |Codacy| image:: https://api.codacy.com/project/badge/Grade/9a34319d917b43219a29e59e9ac75e3b
|
.. |Codacy| image:: https://api.codacy.com/project/badge/Grade/9a34319d917b43219a29e59e9ac75e3b
|
||||||
:alt: Codacy Badge
|
:alt: Codacy Badge
|
||||||
:target: https://app.codacy.com/app/aquatix/digimarks?utm_source=github.com&utm_medium=referral&utm_content=aquatix/digimarks&utm_campaign=badger
|
:target: https://app.codacy.com/app/aquatix/digimarks?utm_source=github.com&utm_medium=referral&utm_content=aquatix/digimarks&utm_campaign=badger
|
||||||
.. _hook settings: https://github.com/aquatix/digimarks/blob/master/example_config/examples.yaml
|
.. _hook settings: https://codeberg.org/diginaut/digimarks/blob/master/example_config/examples.yaml
|
||||||
.. _vhost for Apache2.4: https://github.com/aquatix/digimarks/blob/master/example_config/apache_vhost.conf
|
.. _vhost for Apache2.4: https://codeberg.org/diginaut/digimarks/blob/master/example_config/apache_vhost.conf
|
||||||
.. _uwsgi.ini: https://github.com/aquatix/digimarks/blob/master/example_config/uwsgi.ini
|
.. _uwsgi.ini: https://codeberg.org/diginaut/digimarks/blob/master/example_config/uwsgi.ini
|
||||||
.. _Changelog: https://github.com/aquatix/digimarks/blob/master/CHANGELOG.md
|
.. _Changelog: https://codeberg.org/diginaut/digimarks/blob/master/CHANGELOG.md
|
||||||
.. _Freepik: http://www.flaticon.com/free-icon/letter-m_2041
|
.. _Freepik: http://www.flaticon.com/free-icon/letter-m_2041
|
||||||
.. _systemd for digimarks API: https://github.com/aquatix/digimarks/blob/master/example_config/systemd/digimarks.service
|
.. _systemd for digimarks API: https://codeberg.org/diginaut/digimarks/blob/master/example_config/systemd/digimarks.service
|
||||||
.. _gunicorn config: https://github.com/aquatix/digimarks/blob/master/example_config/gunicorn_digimarks_conf.py
|
.. _gunicorn config: https://codeberg.org/diginaut/digimarks/src/branch/master/example_config/uwsgi.ini
|
||||||
.. _more config: https://github.com/aquatix/digimarks/tree/master/example_config
|
.. _more config: https://codeberg.org/diginaut/digimarks/src/branch/master/example_config
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
"""Digimarks project."""
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
"""Alembic environment file for SQLAlchemy."""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from logging.config import fileConfig
|
from logging.config import fileConfig
|
||||||
|
|
||||||
@@ -7,7 +9,7 @@ from sqlalchemy.engine import Connection
|
|||||||
from sqlalchemy.ext.asyncio import async_engine_from_config
|
from sqlalchemy.ext.asyncio import async_engine_from_config
|
||||||
from sqlmodel import SQLModel
|
from sqlmodel import SQLModel
|
||||||
|
|
||||||
from src.digimarks.models import Bookmark, PublicTag, User
|
from src.digimarks.models import Bookmark, PublicTag, User # noqa
|
||||||
|
|
||||||
# this is the Alembic Config object, which provides
|
# this is the Alembic Config object, which provides
|
||||||
# access to the values within the .ini file in use.
|
# access to the values within the .ini file in use.
|
||||||
@@ -56,6 +58,7 @@ def run_migrations_offline() -> None:
|
|||||||
|
|
||||||
|
|
||||||
def do_run_migrations(connection: Connection) -> None:
|
def do_run_migrations(connection: Connection) -> None:
|
||||||
|
"""Run the migrations."""
|
||||||
context.configure(
|
context.configure(
|
||||||
connection=connection,
|
connection=connection,
|
||||||
target_metadata=target_metadata,
|
target_metadata=target_metadata,
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
"""Initial migration
|
"""Initial migration.
|
||||||
|
|
||||||
Revision ID: 115bcd2e1a38
|
Revision ID: 115bcd2e1a38
|
||||||
Revises:
|
Revises:
|
||||||
Create Date: 2025-09-12 16:06:16.479075
|
Create Date: 2025-09-12 16:06:16.479075
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Sequence, Union
|
from typing import Sequence, Union
|
||||||
|
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
from alembic import op
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
revision: str = '115bcd2e1a38'
|
revision: str = '115bcd2e1a38'
|
||||||
@@ -21,38 +20,41 @@ depends_on: Union[str, Sequence[str], None] = None
|
|||||||
def upgrade() -> None:
|
def upgrade() -> None:
|
||||||
"""Upgrade schema."""
|
"""Upgrade schema."""
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
op.create_table('bookmark',
|
op.create_table(
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
'bookmark',
|
||||||
sa.Column('userkey', sa.String(length=255), nullable=False),
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
sa.Column('title', sa.String(length=255), nullable=False),
|
sa.Column('userkey', sa.String(length=255), nullable=False),
|
||||||
sa.Column('url', sa.String(length=255), nullable=False),
|
sa.Column('title', sa.String(length=255), nullable=False),
|
||||||
sa.Column('created_date', sa.DateTime(), nullable=False),
|
sa.Column('url', sa.String(length=255), nullable=False),
|
||||||
sa.Column('url_hash', sa.String(length=255), nullable=False),
|
sa.Column('created_date', sa.DateTime(), nullable=False),
|
||||||
sa.Column('tags', sa.String(length=255), nullable=False),
|
sa.Column('url_hash', sa.String(length=255), nullable=False),
|
||||||
sa.Column('http_status', sa.Integer(), nullable=False),
|
sa.Column('tags', sa.String(length=255), nullable=False),
|
||||||
sa.Column('modified_date', sa.DateTime(), nullable=True),
|
sa.Column('http_status', sa.Integer(), nullable=False),
|
||||||
sa.Column('favicon', sa.String(length=255), nullable=True),
|
sa.Column('modified_date', sa.DateTime(), nullable=True),
|
||||||
sa.Column('starred', sa.Boolean(), server_default=sa.text('0'), nullable=True),
|
sa.Column('favicon', sa.String(length=255), nullable=True),
|
||||||
sa.Column('deleted_date', sa.DateTime(), server_default=sa.text('(null)'), nullable=True),
|
sa.Column('starred', sa.Boolean(), server_default=sa.text('0'), nullable=True),
|
||||||
sa.Column('status', sa.Integer(), server_default=sa.text('0'), nullable=True),
|
sa.Column('deleted_date', sa.DateTime(), server_default=sa.text('(null)'), nullable=True),
|
||||||
sa.Column('note', sa.Text(), server_default=sa.text('(null)'), nullable=True),
|
sa.Column('status', sa.Integer(), server_default=sa.text('0'), nullable=True),
|
||||||
sa.PrimaryKeyConstraint('id')
|
sa.Column('note', sa.Text(), server_default=sa.text('(null)'), nullable=True),
|
||||||
|
sa.PrimaryKeyConstraint('id'),
|
||||||
)
|
)
|
||||||
op.create_table('publictag',
|
op.create_table(
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
'publictag',
|
||||||
sa.Column('tagkey', sa.String(length=255), nullable=False),
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
sa.Column('userkey', sa.String(length=255), nullable=False),
|
sa.Column('tagkey', sa.String(length=255), nullable=False),
|
||||||
sa.Column('tag', sa.String(length=255), nullable=False),
|
sa.Column('userkey', sa.String(length=255), nullable=False),
|
||||||
sa.Column('created_date', sa.DateTime(), server_default=sa.text('(null)'), nullable=True),
|
sa.Column('tag', sa.String(length=255), nullable=False),
|
||||||
sa.PrimaryKeyConstraint('id')
|
sa.Column('created_date', sa.DateTime(), server_default=sa.text('(null)'), nullable=True),
|
||||||
|
sa.PrimaryKeyConstraint('id'),
|
||||||
)
|
)
|
||||||
op.create_table('user',
|
op.create_table(
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
'user',
|
||||||
sa.Column('username', sa.String(length=255), nullable=False),
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
sa.Column('key', sa.String(length=255), nullable=False),
|
sa.Column('username', sa.String(length=255), nullable=False),
|
||||||
sa.Column('created_date', sa.DateTime(), nullable=False),
|
sa.Column('key', sa.String(length=255), nullable=False),
|
||||||
sa.Column('theme', sa.String(length=20), server_default=sa.text("'green'"), nullable=True),
|
sa.Column('created_date', sa.DateTime(), nullable=False),
|
||||||
sa.PrimaryKeyConstraint('id')
|
sa.Column('theme', sa.String(length=20), server_default=sa.text("'green'"), nullable=True),
|
||||||
|
sa.PrimaryKeyConstraint('id'),
|
||||||
)
|
)
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|||||||
@@ -3,15 +3,14 @@
|
|||||||
Revision ID: a8d8e45f60a1
|
Revision ID: a8d8e45f60a1
|
||||||
Revises: 115bcd2e1a38
|
Revises: 115bcd2e1a38
|
||||||
Create Date: 2025-09-12 16:10:41.378716
|
Create Date: 2025-09-12 16:10:41.378716
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from datetime import UTC, datetime
|
||||||
from typing import Sequence, Union
|
from typing import Sequence, Union
|
||||||
|
|
||||||
from alembic import op
|
|
||||||
from datetime import UTC, datetime
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
import sqlmodel
|
import sqlmodel
|
||||||
|
from alembic import op
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
revision: str = 'a8d8e45f60a1'
|
revision: str = 'a8d8e45f60a1'
|
||||||
@@ -24,72 +23,74 @@ def upgrade() -> None:
|
|||||||
"""Upgrade schema."""
|
"""Upgrade schema."""
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
with op.batch_alter_table('bookmark', schema=None) as batch_op:
|
with op.batch_alter_table('bookmark', schema=None) as batch_op:
|
||||||
batch_op.alter_column('note',
|
batch_op.alter_column(
|
||||||
existing_type=sa.TEXT(),
|
'note',
|
||||||
type_=sqlmodel.sql.sqltypes.AutoString(),
|
existing_type=sa.TEXT(),
|
||||||
nullable=True,
|
type_=sqlmodel.sql.sqltypes.AutoString(),
|
||||||
existing_server_default=sa.text('(null)'))
|
nullable=True,
|
||||||
batch_op.alter_column('starred',
|
existing_server_default=sa.text('(null)'),
|
||||||
existing_type=sa.BOOLEAN(),
|
)
|
||||||
nullable=False,
|
batch_op.alter_column(
|
||||||
existing_server_default=sa.text('0'))
|
'starred', existing_type=sa.BOOLEAN(), nullable=False, existing_server_default=sa.text('0')
|
||||||
batch_op.alter_column('modified_date',
|
)
|
||||||
existing_type=sa.DATETIME(),
|
batch_op.alter_column('modified_date', existing_type=sa.DATETIME(), nullable=True)
|
||||||
nullable=True)
|
batch_op.alter_column(
|
||||||
batch_op.alter_column('deleted_date',
|
'deleted_date', existing_type=sa.DATETIME(), nullable=True, existing_server_default=sa.text('(null)')
|
||||||
existing_type=sa.DATETIME(),
|
)
|
||||||
nullable=True,
|
batch_op.alter_column(
|
||||||
existing_server_default=sa.text('(null)'))
|
'status', existing_type=sa.INTEGER(), nullable=False, existing_server_default=sa.text('0')
|
||||||
batch_op.alter_column('status',
|
)
|
||||||
existing_type=sa.INTEGER(),
|
|
||||||
nullable=False,
|
|
||||||
existing_server_default=sa.text('0'))
|
|
||||||
batch_op.create_foreign_key('bookmark_user', 'user', ['userkey'], ['key'])
|
batch_op.create_foreign_key('bookmark_user', 'user', ['userkey'], ['key'])
|
||||||
with op.batch_alter_table('publictag', schema=None) as batch_op:
|
with op.batch_alter_table('publictag', schema=None) as batch_op:
|
||||||
batch_op.alter_column('created_date',
|
batch_op.alter_column(
|
||||||
existing_type=sa.DATETIME(),
|
'created_date',
|
||||||
nullable=True,
|
existing_type=sa.DATETIME(),
|
||||||
existing_server_default=sa.text(str(datetime.now(UTC))))
|
nullable=True,
|
||||||
|
existing_server_default=sa.text(str(datetime.now(UTC))),
|
||||||
|
)
|
||||||
batch_op.create_foreign_key('publictag_user', 'user', ['userkey'], ['key'])
|
batch_op.create_foreign_key('publictag_user', 'user', ['userkey'], ['key'])
|
||||||
with op.batch_alter_table('user', schema=None) as batch_op:
|
with op.batch_alter_table('user', schema=None) as batch_op:
|
||||||
batch_op.alter_column('theme',
|
batch_op.alter_column(
|
||||||
existing_type=sa.VARCHAR(length=20),
|
'theme', existing_type=sa.VARCHAR(length=20), nullable=False, existing_server_default=sa.text("'green'")
|
||||||
nullable=False,
|
)
|
||||||
existing_server_default=sa.text("'green'"))
|
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
def downgrade() -> None:
|
||||||
"""Downgrade schema."""
|
"""Downgrade schema."""
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
op.alter_column('user', 'theme',
|
op.alter_column(
|
||||||
existing_type=sa.VARCHAR(length=20),
|
'user', 'theme', existing_type=sa.VARCHAR(length=20), nullable=True, existing_server_default=sa.text("'green'")
|
||||||
nullable=True,
|
)
|
||||||
existing_server_default=sa.text("'green'"))
|
|
||||||
op.drop_constraint(None, 'publictag', type_='foreignkey')
|
op.drop_constraint(None, 'publictag', type_='foreignkey')
|
||||||
op.alter_column('publictag', 'created_date',
|
op.alter_column(
|
||||||
existing_type=sa.DATETIME(),
|
'publictag',
|
||||||
nullable=True,
|
'created_date',
|
||||||
existing_server_default=sa.text('(null)'))
|
existing_type=sa.DATETIME(),
|
||||||
|
nullable=True,
|
||||||
|
existing_server_default=sa.text('(null)'),
|
||||||
|
)
|
||||||
op.drop_constraint(None, 'bookmark', type_='foreignkey')
|
op.drop_constraint(None, 'bookmark', type_='foreignkey')
|
||||||
op.alter_column('bookmark', 'status',
|
op.alter_column(
|
||||||
existing_type=sa.INTEGER(),
|
'bookmark', 'status', existing_type=sa.INTEGER(), nullable=True, existing_server_default=sa.text('0')
|
||||||
nullable=True,
|
)
|
||||||
existing_server_default=sa.text('0'))
|
op.alter_column(
|
||||||
op.alter_column('bookmark', 'deleted_date',
|
'bookmark',
|
||||||
existing_type=sa.DATETIME(),
|
'deleted_date',
|
||||||
nullable=True,
|
existing_type=sa.DATETIME(),
|
||||||
existing_server_default=sa.text('(null)'))
|
nullable=True,
|
||||||
op.alter_column('bookmark', 'modified_date',
|
existing_server_default=sa.text('(null)'),
|
||||||
existing_type=sa.DATETIME(),
|
)
|
||||||
nullable=True)
|
op.alter_column('bookmark', 'modified_date', existing_type=sa.DATETIME(), nullable=True)
|
||||||
op.alter_column('bookmark', 'starred',
|
op.alter_column(
|
||||||
existing_type=sa.BOOLEAN(),
|
'bookmark', 'starred', existing_type=sa.BOOLEAN(), nullable=True, existing_server_default=sa.text('0')
|
||||||
nullable=True,
|
)
|
||||||
existing_server_default=sa.text('0'))
|
op.alter_column(
|
||||||
op.alter_column('bookmark', 'note',
|
'bookmark',
|
||||||
existing_type=sqlmodel.sql.sqltypes.AutoString(),
|
'note',
|
||||||
type_=sa.TEXT(),
|
existing_type=sqlmodel.sql.sqltypes.AutoString(),
|
||||||
nullable=True,
|
type_=sa.TEXT(),
|
||||||
existing_server_default=sa.text('(null)'))
|
nullable=True,
|
||||||
|
existing_server_default=sa.text('(null)'),
|
||||||
|
)
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
"""Renamed keys
|
"""Renamed keys.
|
||||||
|
|
||||||
Revision ID: b8cbc6957df5
|
Revision ID: b8cbc6957df5
|
||||||
Revises: a8d8e45f60a1
|
Revises: a8d8e45f60a1
|
||||||
Create Date: 2025-09-12 22:26:38.684120
|
Create Date: 2025-09-12 22:26:38.684120
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Sequence, Union
|
from typing import Sequence, Union
|
||||||
|
|
||||||
from alembic import op
|
from alembic import op
|
||||||
import sqlalchemy as sa
|
|
||||||
import sqlmodel
|
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
revision: str = 'b8cbc6957df5'
|
revision: str = 'b8cbc6957df5'
|
||||||
|
|||||||
@@ -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.10"
|
requires-python = ">=3.11"
|
||||||
keywords = ["bookmarks", "api"]
|
keywords = ["bookmarks", "api"]
|
||||||
license = { text = "Apache" }
|
license = { text = "Apache" }
|
||||||
classifiers = [
|
classifiers = [
|
||||||
@@ -30,11 +30,16 @@ dependencies = [
|
|||||||
"feedgen",
|
"feedgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
server = [
|
||||||
|
"uvicorn",
|
||||||
|
]
|
||||||
|
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
dev = [
|
dev = [
|
||||||
{include-group = "lint"},
|
{ include-group = "lint" },
|
||||||
{include-group = "pub"},
|
{ include-group = "pub" },
|
||||||
{include-group = "test"}
|
{ include-group = "test" }
|
||||||
]
|
]
|
||||||
test = [
|
test = [
|
||||||
"pytest>=7.0.0",
|
"pytest>=7.0.0",
|
||||||
@@ -42,7 +47,7 @@ test = [
|
|||||||
]
|
]
|
||||||
lint = [
|
lint = [
|
||||||
"ruff>=0.1.0",
|
"ruff>=0.1.0",
|
||||||
"mypy>=1.0.0",
|
"pyrefly",
|
||||||
]
|
]
|
||||||
# Publishing on PyPI
|
# Publishing on PyPI
|
||||||
pub = [
|
pub = [
|
||||||
@@ -58,8 +63,8 @@ server = [
|
|||||||
my-script = "digimarks:app"
|
my-script = "digimarks:app"
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
"Homepage" = "https://github.com/aquatix/digimarks"
|
"Homepage" = "https://codeberg.org/diginaut/digimarks"
|
||||||
"Bug Tracker" = "https://github.com/aquatix/digimarks/issues"
|
"Bug Tracker" = "https://codeberg.org/diginaut/digimarks/issues"
|
||||||
|
|
||||||
[tool.black]
|
[tool.black]
|
||||||
line-length = 120
|
line-length = 120
|
||||||
|
|||||||
@@ -2,9 +2,12 @@
|
|||||||
|
|
||||||
# Linting and fixing, including isort
|
# Linting and fixing, including isort
|
||||||
ruff
|
ruff
|
||||||
|
# Typing
|
||||||
|
pyrefly
|
||||||
|
|
||||||
# Test suite
|
# Test suite
|
||||||
pytest
|
pytest
|
||||||
|
pytest-cov
|
||||||
|
|
||||||
# Publishing on PyPI
|
# Publishing on PyPI
|
||||||
build
|
build
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Core application
|
# Core application
|
||||||
fastapi[all]
|
fastapi[all]
|
||||||
sqlmodel
|
sqlmodel
|
||||||
sqlalchemy
|
sqlalchemy[asyncio]
|
||||||
pydantic
|
pydantic
|
||||||
pydantic_settings
|
pydantic_settings
|
||||||
alembic
|
alembic
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
"""digimarks main module."""
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
"""Top-level package for Digimarks."""
|
||||||
|
|
||||||
|
__author__ = """Michiel Scholten"""
|
||||||
|
|||||||
@@ -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,8 +35,11 @@ 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
|
||||||
|
logger.info('HTTP status code %s for %s', bookmark.http_status, bookmark.url)
|
||||||
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?"
|
||||||
logger.error('Exception when trying to retrieve title for %s. Error: %s', bookmark.url, str(err))
|
logger.error('Exception when trying to retrieve title for %s. Error: %s', bookmark.url, str(err))
|
||||||
@@ -43,11 +47,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
|
||||||
@@ -56,8 +61,8 @@ async def set_information_from_source(bookmark: Bookmark, request: Request) -> B
|
|||||||
# with open(filename, 'wb') as out_file:
|
# with open(filename, 'wb') as out_file:
|
||||||
# shutil.copyfileobj(response.raw, out_file)
|
# shutil.copyfileobj(response.raw, out_file)
|
||||||
|
|
||||||
# Extraction was successful
|
# Extraction was successful
|
||||||
logger.info('Extracting information was successful')
|
logger.info('Extracting information was successful')
|
||||||
return bookmark
|
return bookmark
|
||||||
|
|
||||||
|
|
||||||
@@ -72,11 +77,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
|
||||||
@@ -92,7 +101,10 @@ async def list_bookmarks_for_user(
|
|||||||
offset: int = 0,
|
offset: int = 0,
|
||||||
limit: Annotated[int, Query(le=10000)] = 100,
|
limit: Annotated[int, Query(le=10000)] = 100,
|
||||||
) -> Sequence[Bookmark]:
|
) -> Sequence[Bookmark]:
|
||||||
"""List all bookmarks in the database. By default, 100 items are returned."""
|
"""List all bookmarks in the database. By default, 100 items are returned.
|
||||||
|
|
||||||
|
There is a limit of 10000 items.
|
||||||
|
"""
|
||||||
result = await session.exec(
|
result = await session.exec(
|
||||||
select(Bookmark)
|
select(Bookmark)
|
||||||
.where(Bookmark.user_key == user_key, Bookmark.status != Visibility.DELETED)
|
.where(Bookmark.user_key == user_key, Bookmark.status != Visibility.DELETED)
|
||||||
@@ -121,12 +133,17 @@ async def autocomplete_bookmark(
|
|||||||
user_key: str,
|
user_key: str,
|
||||||
bookmark: Bookmark,
|
bookmark: Bookmark,
|
||||||
strip_params: bool = False,
|
strip_params: bool = False,
|
||||||
):
|
) -> Bookmark:
|
||||||
"""Autofill some fields for this (new) bookmark for user `user_key`."""
|
"""Autofill some fields for this (new) bookmark for user `user_key`."""
|
||||||
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(
|
||||||
@@ -149,12 +166,12 @@ async def add_bookmark(
|
|||||||
user_key: str,
|
user_key: str,
|
||||||
bookmark: Bookmark,
|
bookmark: Bookmark,
|
||||||
strip_params: bool = False,
|
strip_params: bool = False,
|
||||||
):
|
) -> Bookmark:
|
||||||
"""Add new bookmark for user `user_key`."""
|
"""Add new bookmark for user `user_key`."""
|
||||||
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)
|
||||||
|
|
||||||
@@ -171,7 +188,7 @@ async def update_bookmark(
|
|||||||
bookmark: Bookmark,
|
bookmark: Bookmark,
|
||||||
url_hash: str,
|
url_hash: str,
|
||||||
strip_params: bool = False,
|
strip_params: bool = False,
|
||||||
):
|
) -> Bookmark:
|
||||||
"""Update existing bookmark `bookmark_key` for user `user_key`."""
|
"""Update existing bookmark `bookmark_key` for user `user_key`."""
|
||||||
result = await session.exec(
|
result = await session.exec(
|
||||||
select(Bookmark).where(
|
select(Bookmark).where(
|
||||||
@@ -190,7 +207,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()
|
||||||
@@ -202,7 +219,7 @@ async def delete_bookmark(
|
|||||||
session,
|
session,
|
||||||
user_key: str,
|
user_key: str,
|
||||||
url_hash: str,
|
url_hash: str,
|
||||||
):
|
) -> None:
|
||||||
"""(Soft)Delete bookmark `bookmark_key` for user `user_key`."""
|
"""(Soft)Delete bookmark `bookmark_key` for user `user_key`."""
|
||||||
result = await session.get(Bookmark, {'url_hash': url_hash, 'user_key': user_key})
|
result = await session.get(Bookmark, {'url_hash': url_hash, 'user_key': user_key})
|
||||||
bookmark = result
|
bookmark = result
|
||||||
|
|||||||
6
src/digimarks/extract.py
Normal file
6
src/digimarks/extract.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from pydantic import AnyUrl
|
||||||
|
|
||||||
|
|
||||||
|
def extract_contents(title: str, url: AnyUrl, note: str):
|
||||||
|
"""Extract contents from a URL."""
|
||||||
|
return
|
||||||
@@ -4,7 +4,7 @@ import logging
|
|||||||
from collections.abc import Sequence
|
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
|
from typing import Annotated, AsyncGenerator, cast
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from fastapi import Depends, FastAPI, HTTPException, Query, Request
|
from fastapi import Depends, FastAPI, HTTPException, Query, Request
|
||||||
@@ -36,8 +36,8 @@ class Settings(BaseSettings):
|
|||||||
# inside the codebase
|
# inside the codebase
|
||||||
# static_dir: DirectoryPath = Path('digimarks/static')
|
# static_dir: DirectoryPath = Path('digimarks/static')
|
||||||
# template_dir: DirectoryPath = Path('digimarks/templates')
|
# template_dir: DirectoryPath = Path('digimarks/templates')
|
||||||
static_dir: DirectoryPath = 'digimarks/static'
|
static_dir: DirectoryPath = DirectoryPath('digimarks/static')
|
||||||
template_dir: DirectoryPath = 'digimarks/templates'
|
template_dir: DirectoryPath = DirectoryPath('digimarks/templates')
|
||||||
|
|
||||||
media_url: str = '/static/'
|
media_url: str = '/static/'
|
||||||
|
|
||||||
@@ -54,22 +54,32 @@ engine = create_async_engine(f'sqlite+aiosqlite:///{settings.database_file}', co
|
|||||||
|
|
||||||
async def get_session() -> AsyncSession:
|
async def get_session() -> AsyncSession:
|
||||||
"""SQLAlchemy session factory."""
|
"""SQLAlchemy session factory."""
|
||||||
async_session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
|
async_session = sessionmaker(bind=engine, class_=AsyncSession, expire_on_commit=False)
|
||||||
async with async_session() as session:
|
async with async_session() as session:
|
||||||
yield session
|
yield session
|
||||||
|
|
||||||
|
|
||||||
|
# Shorter way of getting the DB session in an endpoint
|
||||||
SessionDep = Annotated[AsyncSession, Depends(get_session)]
|
SessionDep = Annotated[AsyncSession, Depends(get_session)]
|
||||||
|
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def lifespan(the_app: FastAPI):
|
async def lifespan(the_app: FastAPI) -> AsyncGenerator[None, None]:
|
||||||
"""Upon start, initialise an AsyncClient and assign it to an attribute named requests_client on the app object."""
|
"""Upon start, initialise an AsyncClient and assign it to an attribute named requests_client on the app object."""
|
||||||
the_app.requests_client = httpx.AsyncClient()
|
async with httpx.AsyncClient() as requests_client:
|
||||||
yield
|
the_app.state.requests_client = requests_client
|
||||||
await the_app.requests_client.aclose()
|
yield
|
||||||
|
await the_app.state.requests_client.aclose()
|
||||||
|
|
||||||
|
|
||||||
|
async def get_requests_client(request: Request) -> httpx.AsyncClient:
|
||||||
|
"""Get the httpx client from the application object."""
|
||||||
|
return cast(httpx.AsyncClient, request.app.state.requests_client)
|
||||||
|
|
||||||
|
|
||||||
|
# Shorter way of getting the httpx client in an endpoint
|
||||||
|
RequestsDep = Annotated[AsyncSession, Depends(get_requests_client)]
|
||||||
|
|
||||||
app = FastAPI(lifespan=lifespan)
|
app = FastAPI(lifespan=lifespan)
|
||||||
app.mount('/static', StaticFiles(directory=settings.static_dir), name='static')
|
app.mount('/static', StaticFiles(directory=settings.static_dir), name='static')
|
||||||
app.mount('/content/favicons', StaticFiles(directory=settings.favicons_dir), name='favicons')
|
app.mount('/content/favicons', StaticFiles(directory=settings.favicons_dir), name='favicons')
|
||||||
@@ -127,7 +137,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) -> 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:
|
||||||
@@ -202,7 +212,7 @@ async def autocomplete_bookmark(
|
|||||||
user_key: str,
|
user_key: str,
|
||||||
bookmark: Bookmark,
|
bookmark: Bookmark,
|
||||||
strip_params: bool = False,
|
strip_params: bool = False,
|
||||||
):
|
) -> Bookmark:
|
||||||
"""Autofill some fields for this (new) bookmark for user `user_key`."""
|
"""Autofill some fields for this (new) bookmark for user `user_key`."""
|
||||||
logger.info('Autocompleting bookmark %s for user %s', bookmark.url_hash, user_key)
|
logger.info('Autocompleting bookmark %s for user %s', bookmark.url_hash, user_key)
|
||||||
return await bookmarks_service.autocomplete_bookmark(session, request, user_key, bookmark, strip_params)
|
return await bookmarks_service.autocomplete_bookmark(session, request, user_key, bookmark, strip_params)
|
||||||
@@ -248,7 +258,7 @@ async def delete_bookmark(
|
|||||||
"""(Soft)Delete bookmark `bookmark_key` for user `user_key`."""
|
"""(Soft)Delete bookmark `bookmark_key` for user `user_key`."""
|
||||||
logger.info('Deleting bookmark %s for user %s', url_hash, user_key)
|
logger.info('Deleting bookmark %s for user %s', url_hash, user_key)
|
||||||
try:
|
try:
|
||||||
result = await bookmarks_service.delete_bookmark(session, user_key, url_hash)
|
_ = await bookmarks_service.delete_bookmark(session, user_key, url_hash)
|
||||||
return {'ok': True}
|
return {'ok': True}
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception('Failed to delete bookmark %s', url_hash)
|
logger.exception('Failed to delete bookmark %s', url_hash)
|
||||||
@@ -275,12 +285,24 @@ async def bookmarks_changed_since(
|
|||||||
)
|
)
|
||||||
latest_created_bookmark = result.first()
|
latest_created_bookmark = result.first()
|
||||||
|
|
||||||
latest_modification = max(latest_modified_bookmark.modified_date, latest_created_bookmark.created_date)
|
# There needs to be at least one bookmark of course
|
||||||
|
if latest_created_bookmark:
|
||||||
|
latest_created_datetime = latest_created_bookmark.created_date
|
||||||
|
else:
|
||||||
|
latest_created_datetime = datetime.min
|
||||||
|
|
||||||
|
# We only have a modified datetime when at least one has been edited
|
||||||
|
if latest_modified_bookmark:
|
||||||
|
latest_modified_datetime = latest_modified_bookmark.modified_date
|
||||||
|
else:
|
||||||
|
latest_modified_datetime = datetime.min
|
||||||
|
|
||||||
|
latest_modification = max(latest_modified_datetime, latest_created_datetime)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'current_time': datetime.now(UTC),
|
'current_time': datetime.now(UTC),
|
||||||
'latest_change': latest_modified_bookmark.modified_date,
|
'latest_change': latest_modified_datetime,
|
||||||
'latest_created': latest_created_bookmark.created_date,
|
'latest_created': latest_created_datetime,
|
||||||
'latest_modification': latest_modification,
|
'latest_modification': latest_modification,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,7 +332,7 @@ async def page_user_landing(
|
|||||||
session: SessionDep,
|
session: SessionDep,
|
||||||
request: Request,
|
request: Request,
|
||||||
user_key: str,
|
user_key: str,
|
||||||
):
|
) -> HTMLResponse:
|
||||||
"""HTML page with the main view for the user."""
|
"""HTML page with the main view for the user."""
|
||||||
result = await session.exec(select(User).where(User.key == user_key))
|
result = await session.exec(select(User).where(User.key == user_key))
|
||||||
user = result.first()
|
user = result.first()
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
.thumbnail {
|
.thumbnail {
|
||||||
/*width: 80px;*/
|
/*width: 80px;*/
|
||||||
width: 66;
|
width: 66px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.thumbnail img {
|
.thumbnail img {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* v0.0.2
|
* v0.0.2
|
||||||
*
|
*
|
||||||
* Created by: Michiel Scholten
|
* Created by: Michiel Scholten
|
||||||
* Source: https://github.com/aquatix/digui
|
* Source: https://codeberg.org/diginaut/digui
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** Colours and themes */
|
/** Colours and themes */
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ document.addEventListener('alpine:init', () => {
|
|||||||
this.cache[this.userKey]['tags'] = await tagsResponse.json();
|
this.cache[this.userKey]['tags'] = await tagsResponse.json();
|
||||||
|
|
||||||
/* Filter bookmarks by (blacklisted) tags */
|
/* Filter bookmarks by (blacklisted) tags */
|
||||||
await this.filterBookmarksByTags();
|
this.filterBookmarksByTags();
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -214,15 +214,18 @@ document.addEventListener('alpine:init', () => {
|
|||||||
|
|
||||||
resetEditBookmark() {
|
resetEditBookmark() {
|
||||||
this.bookmarkToEdit = {
|
this.bookmarkToEdit = {
|
||||||
|
'url_hash': '',
|
||||||
'url': '',
|
'url': '',
|
||||||
'title': '',
|
'title': '',
|
||||||
'note': '',
|
'note': '',
|
||||||
'tags': ''
|
'tags': '',
|
||||||
|
'http_status': 0,
|
||||||
|
'strip_params': false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async startAddingBookmark() {
|
async startAddingBookmark() {
|
||||||
/* Open 'add bookmark' page */
|
/* Open 'add bookmark' page */
|
||||||
console.log('Start adding bookmark');
|
console.log('Open "add bookmark" modal');
|
||||||
this.resetEditBookmark();
|
this.resetEditBookmark();
|
||||||
// this.show_bookmark_details = true;
|
// this.show_bookmark_details = true;
|
||||||
const editFormDialog = document.getElementById("editFormDialog");
|
const editFormDialog = document.getElementById("editFormDialog");
|
||||||
@@ -233,7 +236,54 @@ 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 (response.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;
|
||||||
|
this.bookmarkToEdit.http_status = data.http_status;
|
||||||
|
} else {
|
||||||
|
console.log('Error occurred');
|
||||||
|
this.bookmarkToEditError = data.detail;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// enter 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 */
|
|
||||||
//
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<ul>
|
<ul>
|
||||||
<li><h1>digimarks</h1></li>
|
<li><h1>digimarks</h1></li>
|
||||||
<li>
|
<li>
|
||||||
<a class="button" href="https://github.com/aquatix/digimarks">digimarks project page</a>
|
<a class="button" href="https://codeberg.org/diginaut/digimarks">digimarks project page</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
<main>
|
<main>
|
||||||
<h1>Welcome to digimarks, your online bookmarking and notes tool</h1>
|
<h1>Welcome to digimarks, your online bookmarking and notes tool</h1>
|
||||||
|
|
||||||
<p>Please visit your personal url, or <a href="https://github.com/aquatix/digimarks">see the digimarks
|
<p>Please visit your personal url, or <a href="https://codeberg.org/diginaut/digimarks">see the digimarks
|
||||||
project page</a>.</p>
|
project page</a>.</p>
|
||||||
|
|
||||||
<p>If you forgot/lost your personal url, contact your digimarks
|
<p>If you forgot/lost your personal url, contact your digimarks
|
||||||
|
|||||||
@@ -192,6 +192,10 @@
|
|||||||
<input id="bookmark_url" type="text" name="bookmark_url" placeholder="url"
|
<input id="bookmark_url" type="text" name="bookmark_url" placeholder="url"
|
||||||
x-on:change.debounce="$store.digimarks.bookmarkURLChanged()"
|
x-on:change.debounce="$store.digimarks.bookmarkURLChanged()"
|
||||||
x-model="$store.digimarks.bookmarkToEdit.url">
|
x-model="$store.digimarks.bookmarkToEdit.url">
|
||||||
|
<p x-show="$store.digimarks.bookmarkToEdit.http_status > 202"
|
||||||
|
x-text="'HTTP statuscode: ' + $store.digimarks.bookmarkToEdit.http_status" x-cloak
|
||||||
|
class="error"></p>
|
||||||
|
<p>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset class="form-group">
|
<fieldset class="form-group">
|
||||||
<label for="bookmark_title">Title</label>
|
<label for="bookmark_title">Title</label>
|
||||||
@@ -212,16 +216,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