docs: document server-side rendering pipeline, response timing middleware, and Telegram pairing API

- Add comprehensive documentation for backend content rendering in AGENTS.md, detailing the new `render_content` and `render_title` Jinja globals built on mistune with media processing, emoji shortcodes, and XSS protection
- Document the `X-Response-Time` header and bottom-left render time indicator in README.md
- Update bot token pricing documentation to clarify fallback vs gateway cost headers
- Add `email_accounts` to soft-delete tables and `idx_users_role` composite index in database schema
- Implement `telegram_pairings` and `telegram_links` table creation with column migration and indexes
- Add `/profile/{username}/telegram` endpoint to docs API with request/unpair actions
- Register `TelegramService` in main.py lifespan and add `response_timing` middleware emitting `X-Response-Time` header
- Introduce `TelegramPairForm` model and `guard_public_host_sync` synchronous host validation function
This commit is contained in:
2026-06-18 22:09:34 +00:00
parent 95dca73291
commit 6ceca3d0d4
146 changed files with 6079 additions and 392 deletions
+1
View File
@@ -0,0 +1 @@
# retoor <retoor@molodetz.nl>
+113
View File
@@ -0,0 +1,113 @@
# retoor <retoor@molodetz.nl>
from email.message import EmailMessage
import pytest
from devplacepy.services.email.client import (
EmailClient,
_decode_date,
_header_addresses,
imap_quote,
)
from devplacepy.services.email.config import EmailAccount
from devplacepy.services.email.errors import EmailError
def _account(**overrides):
base = {
"label": "work",
"imap_host": "127.0.0.1",
"imap_port": 993,
"imap_ssl": 1,
"smtp_host": "127.0.0.1",
"smtp_port": 587,
"smtp_starttls": 1,
"username": "me@example.com",
"password": "secret",
}
base.update(overrides)
return EmailAccount.from_row(base)
def test_imap_quote_escapes_quotes_and_backslashes():
assert imap_quote('a"b\\c') == '"a\\"b\\\\c"'
def test_header_addresses_extracts_only_addresses():
parsed = _header_addresses("Alice <a@x.dev>, b@y.dev")
assert parsed == ["Alice <a@x.dev>", "b@y.dev"]
def test_header_addresses_empty_is_empty_list():
assert _header_addresses("") == []
def test_decode_date_parses_rfc2822():
iso = _decode_date("Wed, 18 Jun 2025 10:30:00 +0000")
assert iso is not None and iso.startswith("2025-06-18T10:30:00")
def test_decode_date_returns_raw_on_unparseable():
assert _decode_date("not a date") == "not a date"
def test_decode_date_empty_is_none():
assert _decode_date("") is None
def test_imap_without_host_raises_config_error():
client = EmailClient(_account(imap_host=""))
with pytest.raises(EmailError) as exc:
client.list_folders()
assert exc.value.kind == "config"
def test_smtp_without_host_raises_config_error():
client = EmailClient(_account(smtp_host=""))
with pytest.raises(EmailError) as exc:
client.send_message(to=["x@y.dev"], subject="hi", body="b")
assert exc.value.kind == "config"
def test_imap_loopback_host_is_blocked():
client = EmailClient(_account(imap_host="127.0.0.1"))
with pytest.raises(EmailError) as exc:
client.list_folders()
assert exc.value.kind == "blocked"
def test_smtp_loopback_host_is_blocked():
client = EmailClient(_account(smtp_host="127.0.0.1"))
with pytest.raises(EmailError) as exc:
client.send_message(to=["x@y.dev"], subject="hi", body="b")
assert exc.value.kind == "blocked"
def test_render_message_truncates_oversized_body():
client = EmailClient(_account())
message = EmailMessage()
message["From"] = "Alice <a@x.dev>"
message["To"] = "b@y.dev"
message["Subject"] = "topic"
message.set_content("x" * 200_000)
rendered = client._render_message("17", "INBOX", ["\\Seen"], message)
assert rendered["body_truncated"] is True
assert len(rendered["body"]) == 80_000
assert rendered["uid"] == "17"
assert rendered["to"] == ["b@y.dev"]
def test_render_message_lists_attachments():
client = EmailClient(_account())
message = EmailMessage()
message["From"] = "a@x.dev"
message["Subject"] = "with attachment"
message.set_content("body")
message.add_attachment(
b"payload", maintype="application", subtype="pdf", filename="report.pdf"
)
rendered = client._render_message("3", "INBOX", [], message)
assert rendered["body"] == "body"
assert rendered["attachments"][0]["filename"] == "report.pdf"
assert rendered["attachments"][0]["content_type"] == "application/pdf"
+59
View File
@@ -0,0 +1,59 @@
# retoor <retoor@molodetz.nl>
from devplacepy.services.email.config import EmailAccount
def test_defaults_apply_imap_993_and_smtp_587():
account = EmailAccount.from_row({"username": "me@example.com"})
assert account.imap_port == 993
assert account.smtp_port == 587
def test_from_address_falls_back_to_username():
account = EmailAccount.from_row({"username": "me@example.com"})
assert account.from_address == "me@example.com"
def test_explicit_from_address_wins():
account = EmailAccount.from_row(
{"username": "login@example.com", "from_address": "public@example.com"}
)
assert account.from_address == "public@example.com"
def test_boolean_coercion_from_strings():
account = EmailAccount.from_row(
{
"imap_ssl": "true",
"imap_starttls": "0",
"smtp_ssl": "yes",
"smtp_starttls": "on",
}
)
assert account.imap_ssl is True
assert account.imap_starttls is False
assert account.smtp_ssl is True
assert account.smtp_starttls is True
def test_boolean_coercion_from_integers():
account = EmailAccount.from_row({"imap_ssl": 1, "smtp_ssl": 0})
assert account.imap_ssl is True
assert account.smtp_ssl is False
def test_port_coercion_falls_back_on_garbage():
account = EmailAccount.from_row({"imap_port": "not-a-port", "smtp_port": "2525"})
assert account.imap_port == 993
assert account.smtp_port == 2525
def test_missing_fields_become_empty_strings():
account = EmailAccount.from_row({})
assert account.label == ""
assert account.imap_host == ""
assert account.smtp_host == ""
assert account.username == ""
assert account.password == ""
assert account.from_address == ""
assert account.from_name == ""