Compare commits

..

No commits in common. "b415f6265dcf340d62c80598ec6eabec5f42bb48" and "f4ca16d3da42381ace55cd4d425ae4a0ac0df0d3" have entirely different histories.

2 changed files with 21 additions and 72 deletions

17
db.py
View file

@ -131,8 +131,7 @@ def init_db():
" title TEXT," " title TEXT,"
" body TEXT," " body TEXT,"
" note TEXT DEFAULT ''," " note TEXT DEFAULT '',"
" last_modified TEXT DEFAULT (strftime('%Y-%m-%dT%H:%M:%S','now'))," " last_modified TEXT DEFAULT (strftime('%Y-%m-%dT%H:%M:%S','now'))"
" reticulum_dest TEXT DEFAULT ''"
")" ")"
) )
db.execute( db.execute(
@ -248,11 +247,6 @@ def init_db():
db.execute("ALTER TABLE pages ADD COLUMN summary TEXT DEFAULT ''") db.execute("ALTER TABLE pages ADD COLUMN summary TEXT DEFAULT ''")
db.commit() db.commit()
# Migrate pages: add reticulum_dest column if missing
if "reticulum_dest" not in page_cols:
db.execute("ALTER TABLE pages ADD COLUMN reticulum_dest TEXT DEFAULT ''")
db.commit()
# Chunks table for semantic search embeddings # Chunks table for semantic search embeddings
db.execute( db.execute(
"CREATE TABLE IF NOT EXISTS chunks (" "CREATE TABLE IF NOT EXISTS chunks ("
@ -365,18 +359,19 @@ def fetch_page(url):
def index_url(url, note="", reticulum_dest=""): def index_url(url, note=""):
url = clean_url(url) url = clean_url(url)
title, body, links, meta_desc = fetch_page(url) title, body, links, meta_desc = fetch_page(url)
# Use meta description if available and meaningful, otherwise generate from body
summary = meta_desc if meta_desc and len(meta_desc) > 20 else "" summary = meta_desc if meta_desc and len(meta_desc) > 20 else ""
db = get_db() db = get_db()
try: try:
now = __import__("datetime").datetime.now().strftime("%Y-%m-%dT%H:%M:%S") now = __import__("datetime").datetime.now().strftime("%Y-%m-%dT%H:%M:%S")
db.execute( db.execute(
"INSERT INTO pages (url, title, body, note, last_modified, summary, reticulum_dest) VALUES (?, ?, ?, ?, ?, ?, ?) " "INSERT INTO pages (url, title, body, note, last_modified, summary) VALUES (?, ?, ?, ?, ?, ?) "
"ON CONFLICT(url) DO UPDATE SET title=excluded.title, body=excluded.body, " "ON CONFLICT(url) DO UPDATE SET title=excluded.title, body=excluded.body, "
"note=excluded.note, last_modified=excluded.last_modified, summary=excluded.summary, reticulum_dest=excluded.reticulum_dest", "note=excluded.note, last_modified=excluded.last_modified, summary=excluded.summary",
(url, title, body, note, now, summary, reticulum_dest), (url, title, body, note, now, summary),
) )
page_id = db.execute("SELECT id FROM pages WHERE url = ?", (url,)).fetchone()[0] page_id = db.execute("SELECT id FROM pages WHERE url = ?", (url,)).fetchone()[0]
db.execute("DELETE FROM links WHERE page_id = ?", (page_id,)) db.execute("DELETE FROM links WHERE page_id = ?", (page_id,))

View file

@ -338,23 +338,9 @@ def handle_search(query):
) )
def handle_add_form(msg="", action_type="index"): def handle_add_form(msg=""):
if action_type == "subscribe":
return _respond(
f"<h1>subscribe</h1>"
f"<p>Subscribe to a friend's TinyWeb instance to sync their index</p>"
f'<form method="post" action="/subscriptions/add">'
f'{_csrf_field()}'
f'<input name="dest_hash" placeholder="destination hash (32 hex chars)" size="50"><br><br>'
f'<button type="submit">subscribe</button>'
f"</form>"
f"<p><small>or <a href=\"/add\">add a single site</a></small></p>"
f"<p>{msg}</p>"
f'<a href="/">back</a>'
)
return _respond( return _respond(
f"<h1>add url</h1>" f"<h1>add url</h1>"
f"<p>Add a site to your index</p>"
f'<form method="post" action="/add">' f'<form method="post" action="/add">'
f'{_csrf_field()}' f'{_csrf_field()}'
f'<input name="url" placeholder="https://example.com" size="50"><br><br>' f'<input name="url" placeholder="https://example.com" size="50"><br><br>'
@ -368,27 +354,18 @@ def handle_add_form(msg="", action_type="index"):
def handle_add_submit(body): def handle_add_submit(body):
input_type = body.get("input_type", ["url"])[0] url = clean_url(body.get("url", [""])[0].strip())
url = body.get("url", [""])[0].strip()
reticulum_dest = body.get("reticulum_dest", [""])[0].strip().replace("<", "").replace(">", "")
note = body.get("note", [""])[0].strip() note = body.get("note", [""])[0].strip()
tags = body.get("tags", [""])[0].strip() tags = body.get("tags", [""])[0].strip()
if input_type == "url": if not url:
if not url: return handle_add_form("URL is required.")
return handle_add_form("URL is required.") if not url.startswith(("http://", "https://")):
url = clean_url(url) return handle_add_form("URL must start with http:// or https://")
if not url.startswith(("http://", "https://")):
return handle_add_form("URL must start with http:// or https://")
else:
if not reticulum_dest:
return handle_add_form("Reticulum destination hash is required.")
if len(reticulum_dest) != 32 or not all(c in "0123456789abcdefABCDEF" for c in reticulum_dest):
return handle_add_form("Invalid reticulum destination hash. Must be 32 hex characters.")
url = f"reticulum:{reticulum_dest}"
# Try auto-index first
try: try:
title = index_url(url, note, reticulum_dest if reticulum_dest else "") title = index_url(url, note)
if tags: if tags:
db = get_db() db = get_db()
try: try:
@ -398,8 +375,7 @@ def handle_add_submit(body):
db.commit() db.commit()
finally: finally:
return_db(db) return_db(db)
return handle_add_form(f'Indexed: <a href="{esc(url)}">{esc(title)}</a>')
return handle_add_form(f'Indexed: {esc(url)}')
except ValueError as e: except ValueError as e:
return handle_add_form(f"Error: {esc(str(e))}") return handle_add_form(f"Error: {esc(str(e))}")
@ -438,7 +414,6 @@ def handle_add_manual_submit(body):
if not url: if not url:
return handle_add_form("URL is required.") return handle_add_form("URL is required.")
if not manual_title or not manual_desc: if not manual_title or not manual_desc:
return handle_add_form("Title and description are required for manual entry.") return handle_add_form("Title and description are required for manual entry.")
@ -446,6 +421,7 @@ def handle_add_manual_submit(body):
try: try:
now = __import__("datetime").datetime.now().strftime("%Y-%m-%dT%H:%M:%S") now = __import__("datetime").datetime.now().strftime("%Y-%m-%dT%H:%M:%S")
# Insert the page
db.execute( db.execute(
"INSERT INTO pages (url, title, body, note, last_modified, summary) VALUES (?, ?, ?, ?, ?, ?) " "INSERT INTO pages (url, title, body, note, last_modified, summary) VALUES (?, ?, ?, ?, ?, ?) "
"ON CONFLICT(url) DO UPDATE SET title=excluded.title, body=excluded.body, " "ON CONFLICT(url) DO UPDATE SET title=excluded.title, body=excluded.body, "
@ -479,8 +455,6 @@ def handle_add_manual_submit(body):
def handle_pages(query=None): def handle_pages(query=None):
msg = query.get("msg", [""])[0] if query else ""
msg_html = f'<p class="success">{esc(msg)}</p>' if msg else ""
page = _paginate(query or {}) page = _paginate(query or {})
offset = (page - 1) * BROWSE_PER_PAGE offset = (page - 1) * BROWSE_PER_PAGE
db = get_db() db = get_db()
@ -508,7 +482,6 @@ def handle_pages(query=None):
return_db(db) return_db(db)
return _respond( return _respond(
f"<h1>indexed pages ({total})</h1>" f"<h1>indexed pages ({total})</h1>"
f"{msg_html}"
f"<ul>{items}</ul>" f"<ul>{items}</ul>"
f'{_page_nav(page, total, "/pages", BROWSE_PER_PAGE)}' f'{_page_nav(page, total, "/pages", BROWSE_PER_PAGE)}'
f'<p><a href="/export">export</a> | <a href="/import">import</a></p>' f'<p><a href="/export">export</a> | <a href="/import">import</a></p>'
@ -519,27 +492,20 @@ def handle_pages(query=None):
def handle_edit_form(page_id, msg=""): def handle_edit_form(page_id, msg=""):
db = get_db() db = get_db()
try: try:
row = db.execute("SELECT id, url, title, body, note, summary FROM pages WHERE id = ?", (page_id,)).fetchone() row = db.execute("SELECT id, url, title, note FROM pages WHERE id = ?", (page_id,)).fetchone()
if not row: if not row:
return _error(404) return _error(404)
tags = ", ".join(_get_page_tags(page_id, db)) tags = ", ".join(_get_page_tags(page_id, db))
finally: finally:
return_db(db) return_db(db)
return _respond( return _respond(
f"<h1>edit page</h1>" f"<h1>edit page</h1>"
f"<p><b>{esc(row['title'])}</b><br>" f"<p><b>{esc(row['title'])}</b><br>"
f"<small>{esc(row['url'])}</small></p>" f"<small>{esc(row['url'])}</small></p>"
f'<form method="post" action="/edit/{row["id"]}">' f'<form method="post" action="/edit/{row["id"]}">'
f'{_csrf_field()}' f'{_csrf_field()}'
f'<label>Title:</label><br>' f'<input name="note" value="{esc(row["note"])}" placeholder="why did you save this?" size="50"><br><br>'
f'<input name="title" value="{esc(row["title"])}" size="60"><br><br>' f'<input name="tags" value="{esc(tags)}" placeholder="tags (comma-separated)" size="50"><br><br>'
f'<label>Summary (shown in search results):</label><br>'
f'<textarea name="summary" rows="3" cols="60">{esc(row["summary"] or "")}</textarea><br><br>'
f'<label>Note (why you saved this):</label><br>'
f'<input name="note" value="{esc(row["note"])}" size="50"><br><br>'
f'<label>Tags (comma-separated):</label><br>'
f'<input name="tags" value="{esc(tags)}" size="50"><br><br>'
f'<button type="submit">save</button>' f'<button type="submit">save</button>'
f"</form>" f"</form>"
f"<p>{msg}</p>" f"<p>{msg}</p>"
@ -548,25 +514,15 @@ def handle_edit_form(page_id, msg=""):
def handle_edit_submit(page_id, body): def handle_edit_submit(page_id, body):
title = body.get("title", [""])[0].strip()
summary = body.get("summary", [""])[0].strip()
note = body.get("note", [""])[0].strip() note = body.get("note", [""])[0].strip()
tags = body.get("tags", [""])[0].strip() tags = body.get("tags", [""])[0].strip()
db = get_db() db = get_db()
try: try:
db.execute( db.execute("UPDATE pages SET note = ? WHERE id = ?", (note, page_id))
"UPDATE pages SET title = ?, summary = ?, note = ? WHERE id = ?",
(title, summary, note, page_id)
)
_set_page_tags(page_id, tags, db) _set_page_tags(page_id, tags, db)
db.commit() db.commit()
finally: finally:
return_db(db) return_db(db)
return _redirect("/pages") return _redirect("/pages")
@ -925,7 +881,6 @@ def handle_subscriptions(msg=""):
f'<input name="dest_hash" placeholder="destination hash" size="40"> ' f'<input name="dest_hash" placeholder="destination hash" size="40"> '
f'<button>subscribe</button>' f'<button>subscribe</button>'
f'</form>' f'</form>'
f'<p><small>or <a href="/add?type=subscribe">subscribe to an instance</a></small></p>'
f'<p>{msg}</p>' f'<p>{msg}</p>'
f'<hr>{listing}' f'<hr>{listing}'
f'<br><a href="/">back</a>' f'<br><a href="/">back</a>'
@ -1291,8 +1246,7 @@ def _dispatch_inner(data):
if path == "/": if path == "/":
return handle_search(query) return handle_search(query)
elif path == "/add": elif path == "/add":
action_type = query.get("type", ["index"])[0] return handle_add_form()
return handle_add_form(action_type=action_type if action_type == "subscribe" else "index")
elif path == "/pages": elif path == "/pages":
return handle_pages(query) return handle_pages(query)
elif path.startswith("/edit/"): elif path.startswith("/edit/"):