Decoupling audit cleanup: fix protocol gaps, remove dead APIs
- Add search_posts, entry_ids_for_content, visible_entries_for_period to protocols and stubs - Delete internal_api.py and factory cleanup hook (zero callers) - Convert utils.py to utils/ package with calendar_helpers module - Remove deleted_at check from calendar_view template (service filters) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
101
utils/__init__.py
Normal file
101
utils/__init__.py
Normal file
@@ -0,0 +1,101 @@
|
||||
import re
|
||||
from bs4 import BeautifulSoup
|
||||
import json
|
||||
import os
|
||||
from typing import Iterable, Union, List
|
||||
|
||||
from quart import request
|
||||
|
||||
def soup_of(html: str) -> BeautifulSoup:
|
||||
return BeautifulSoup(html, "lxml")
|
||||
|
||||
def normalize_text(s: str) -> str:
|
||||
return re.sub(r"\s+", " ", (s or "").strip())
|
||||
|
||||
def log(msg: str) -> None:
|
||||
print(msg, flush=True)
|
||||
|
||||
def ensure_dir(path: str) -> None:
|
||||
os.makedirs(path, exist_ok=True)
|
||||
|
||||
def dump_json(path: str, data) -> None:
|
||||
ensure_dir(os.path.dirname(path))
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
json.dump(data, f, ensure_ascii=False, indent=2)
|
||||
|
||||
|
||||
def _join_url_parts(parts: List[str]) -> str:
|
||||
parts = [p for p in parts if p is not None and p != ""]
|
||||
if not parts:
|
||||
return ""
|
||||
|
||||
# Preserve scheme like "https://"
|
||||
m = re.match(r"^([a-zA-Z][a-zA-Z0-9+.-]*://)(.*)$", parts[0])
|
||||
if m:
|
||||
scheme, first = m.group(1), m.group(2)
|
||||
else:
|
||||
scheme, first = "", parts[0]
|
||||
|
||||
cleaned = [first.strip("/")]
|
||||
for seg in parts[1:]:
|
||||
seg = str(seg)
|
||||
# If a later segment is already an absolute URL, use it as the base
|
||||
m2 = re.match(r"^([a-zA-Z][a-zA-Z0-9+.-]*://)(.*)$", seg)
|
||||
if m2:
|
||||
scheme, first = m2.group(1), m2.group(2)
|
||||
cleaned = [first.strip("/")]
|
||||
elif seg.startswith("?") or seg.startswith("#"):
|
||||
cleaned[-1] = cleaned[-1] + seg # attach query/fragment
|
||||
else:
|
||||
cleaned.append(seg.strip("/"))
|
||||
|
||||
url = scheme + "/".join(s for s in cleaned if s != "")
|
||||
|
||||
# Preserve trailing slash if caller's last segment had one (and isn't ? or #)
|
||||
last = str(parts[-1])
|
||||
if last.endswith("/") and not last.startswith(("?", "#")) and not url.endswith("/"):
|
||||
url += "/"
|
||||
|
||||
return url
|
||||
|
||||
def hx_fragment_request() -> bool:
|
||||
return request.headers.get("HX-Request", "").lower() == "true"
|
||||
def route_prefix():
|
||||
return f"{request.scheme}://{request.host}/{request.headers.get('x-forwarded-prefix', '/')}"
|
||||
|
||||
def join_url(value: Union[str, Iterable[str]]):
|
||||
if isinstance(value, str):
|
||||
parts = [value]
|
||||
else:
|
||||
parts = list(value)
|
||||
return _join_url_parts(parts)
|
||||
|
||||
def host_url(value: str='', no_slash=False):
|
||||
"""
|
||||
Join g.route with value and ensure the resulting URL has a trailing slash
|
||||
on the path, but never after query/fragment.
|
||||
|
||||
Examples:
|
||||
http://jjj -> http://jjj/
|
||||
http://jjj?hello -> http://jjj/?hello
|
||||
/foo -> /foo/
|
||||
/foo?x=1#frag -> /foo/?x=1#frag
|
||||
"""
|
||||
url = join_url([route_prefix(), value])
|
||||
|
||||
# Ensure trailing slash on the PATH (before ? or #)
|
||||
# Split into: base (no ?/#), optional ?query, optional #fragment
|
||||
if no_slash:
|
||||
return url
|
||||
m = re.match(r'^(?P<base>[^?#]*)(?P<qs>\?[^#]*)?(?P<frag>#.*)?$', url)
|
||||
if not m:
|
||||
return url # fallback: return as-is
|
||||
|
||||
base = m.group('base') or ""
|
||||
qs = m.group('qs') or ""
|
||||
frag = m.group('frag') or ""
|
||||
|
||||
if base and not base.endswith('/'):
|
||||
base += '/'
|
||||
|
||||
return f"{base}{qs}{frag}"
|
||||
54
utils/calendar_helpers.py
Normal file
54
utils/calendar_helpers.py
Normal file
@@ -0,0 +1,54 @@
|
||||
"""Pure calendar utility functions (no ORM dependencies).
|
||||
|
||||
Extracted from events/bp/calendar/services/calendar_view.py so that
|
||||
blog admin (and any other app) can use them without cross-app imports.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import calendar as pycalendar
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from quart import request
|
||||
|
||||
|
||||
def parse_int_arg(name: str, default: int | None = None) -> int | None:
|
||||
"""Parse an integer query parameter from the request."""
|
||||
val = request.args.get(name, "").strip()
|
||||
if not val:
|
||||
return default
|
||||
try:
|
||||
return int(val)
|
||||
except ValueError:
|
||||
return default
|
||||
|
||||
|
||||
def add_months(year: int, month: int, delta: int) -> tuple[int, int]:
|
||||
"""Add (or subtract) months to a given year/month, handling year overflow."""
|
||||
new_month = month + delta
|
||||
new_year = year + (new_month - 1) // 12
|
||||
new_month = ((new_month - 1) % 12) + 1
|
||||
return new_year, new_month
|
||||
|
||||
|
||||
def build_calendar_weeks(year: int, month: int) -> list[list[dict]]:
|
||||
"""Build a calendar grid for the given year and month.
|
||||
|
||||
Returns a list of weeks, where each week is a list of 7 day dicts.
|
||||
"""
|
||||
today = datetime.now(timezone.utc).date()
|
||||
cal = pycalendar.Calendar(firstweekday=0) # 0 = Monday
|
||||
weeks: list[list[dict]] = []
|
||||
|
||||
for week in cal.monthdatescalendar(year, month):
|
||||
week_days = []
|
||||
for d in week:
|
||||
week_days.append(
|
||||
{
|
||||
"date": d,
|
||||
"in_month": (d.month == month),
|
||||
"is_today": (d == today),
|
||||
}
|
||||
)
|
||||
weeks.append(week_days)
|
||||
|
||||
return weeks
|
||||
Reference in New Issue
Block a user