8 major features: trafilatura, digest, ntfy actions, templates, FTS5 search, backup/restore, proxy, RSS reader
build-and-push / docker (push) Has been cancelled

- Full article extraction via trafilatura (fetch_full_article)
- Digest mode with configurable period (digest_enabled, digest_period_hours)
- ntfy Actions buttons (Open article, Open feed)
- Notification templates with {title}, {body}, {link}, {source}, {image_url}
- FTS5 full-text search in notification history
- Database backup/restore (download/upload .db)
- HTTP/SOCKS proxy for RSS feed fetching (proxy_url setting)
- Built-in RSS reader tab with categories, unread counts, article detail view
- Auto-category 'Общее' for feeds without a category
- Article storage (Article table) for reader
- DigestEntry model for pending digest entries

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
dimon
2026-06-03 20:47:46 +08:00
parent f8d2c31658
commit 834092a3ec
13 changed files with 1414 additions and 44 deletions
+37 -1
View File
@@ -9,7 +9,7 @@ from sqlmodel import Session, SQLModel, create_engine, select
from . import config
from .auth import hash_password
from .models import Settings, User
from .models import Category, Settings, User
engine = create_engine(
config.DATABASE_URL,
@@ -18,6 +18,33 @@ engine = create_engine(
)
def _setup_fts() -> None:
"""Create FTS5 virtual table and triggers for full-text notification search."""
with engine.begin() as conn:
conn.execute(text(
"CREATE VIRTUAL TABLE IF NOT EXISTS notification_fts "
"USING fts5(title, body, content='notification', content_rowid='id')"
))
# Triggers to keep FTS index in sync
for trigger_sql in [
"""CREATE TRIGGER IF NOT EXISTS ntf_ai AFTER INSERT ON notification BEGIN
INSERT INTO notification_fts(rowid, title, body)
VALUES (new.id, new.title, new.feed_title);
END""",
"""CREATE TRIGGER IF NOT EXISTS ntf_ad AFTER DELETE ON notification BEGIN
INSERT INTO notification_fts(notification_fts, rowid, title, body)
VALUES ('delete', old.id, old.title, old.feed_title);
END""",
"""CREATE TRIGGER IF NOT EXISTS ntf_au AFTER UPDATE ON notification BEGIN
INSERT INTO notification_fts(notification_fts, rowid, title, body)
VALUES ('delete', old.id, old.title, old.feed_title);
INSERT INTO notification_fts(rowid, title, body)
VALUES (new.id, new.title, new.feed_title);
END""",
]:
conn.execute(text(trigger_sql))
def _migrate() -> None:
"""Add any model columns missing from existing tables (SQLite ALTER ADD).
@@ -60,6 +87,7 @@ def init_db() -> None:
"""Create tables, run migration, ensure settings + admin user exist."""
SQLModel.metadata.create_all(engine)
_migrate()
_setup_fts()
with Session(engine) as session:
if session.get(Settings, 1) is None:
session.add(
@@ -83,6 +111,14 @@ def init_db() -> None:
)
session.commit()
# Bootstrap the "Общее" (General) category.
general = session.exec(
select(Category).where(Category.name == "Общее")
).first()
if general is None:
session.add(Category(name="Общее", sort_order=0))
session.commit()
def get_settings(session: Session) -> Settings:
settings = session.get(Settings, 1)