fix: address bug review findings
- Debounce MutationObserver (200ms) and scope to #modelGrid when available - Sanitize data.updated via parseInt before innerHTML to prevent XSS - Guard pagination against page_size=0 (division by zero) and negative values - Reject non-POST requests on /api/lm-extra/fetch-stats (state-mutating) - Early return in upsert_batch for empty rows list Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -284,8 +284,8 @@ async def _proxy_list_with_stats_sort(
|
|||||||
"""Fetch all items from remote, sort by stats locally, paginate."""
|
"""Fetch all items from remote, sort by stats locally, paginate."""
|
||||||
from urllib.parse import urlencode
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
page = int(request.query.get("page", "1"))
|
page = max(1, int(request.query.get("page", "1")))
|
||||||
page_size = int(request.query.get("page_size", "20"))
|
page_size = max(1, int(request.query.get("page_size", "20")))
|
||||||
sort_key, _, order = sort_by.partition(":")
|
sort_key, _, order = sort_by.partition(":")
|
||||||
order = order or "desc"
|
order = order or "desc"
|
||||||
|
|
||||||
@@ -342,6 +342,8 @@ async def _proxy_list_with_stats_sort(
|
|||||||
|
|
||||||
async def _handle_fetch_stats(request: web.Request) -> web.Response:
|
async def _handle_fetch_stats(request: web.Request) -> web.Response:
|
||||||
"""Bulk fetch CivitAI stats for all models."""
|
"""Bulk fetch CivitAI stats for all models."""
|
||||||
|
if request.method != "POST":
|
||||||
|
return web.json_response({"error": "POST required"}, status=405)
|
||||||
try:
|
try:
|
||||||
from .stats_service import StatsFetchService
|
from .stats_service import StatsFetchService
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
|||||||
@@ -174,7 +174,8 @@
|
|||||||
const resp = await _origFetch("/api/lm-extra/fetch-stats", { method: "POST" });
|
const resp = await _origFetch("/api/lm-extra/fetch-stats", { method: "POST" });
|
||||||
const data = await resp.json();
|
const data = await resp.json();
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
btn.innerHTML = `<i class="fas fa-check"></i> <span>${data.updated} updated</span>`;
|
const count = parseInt(data.updated, 10) || 0;
|
||||||
|
btn.innerHTML = `<i class="fas fa-check"></i> <span>${count} updated</span>`;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
btn.innerHTML = '<i class="fas fa-chart-bar"></i> <span>Fetch Stats</span>';
|
btn.innerHTML = '<i class="fas fa-chart-bar"></i> <span>Fetch Stats</span>';
|
||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
@@ -205,13 +206,19 @@
|
|||||||
addFetchStatsButton();
|
addFetchStatsButton();
|
||||||
patchCards();
|
patchCards();
|
||||||
|
|
||||||
|
let debounceTimer = null;
|
||||||
const observer = new MutationObserver(() => {
|
const observer = new MutationObserver(() => {
|
||||||
|
if (debounceTimer) return;
|
||||||
|
debounceTimer = setTimeout(() => {
|
||||||
|
debounceTimer = null;
|
||||||
patchCards();
|
patchCards();
|
||||||
patchSortDropdown();
|
patchSortDropdown();
|
||||||
addFetchStatsButton();
|
addFetchStatsButton();
|
||||||
|
}, 200);
|
||||||
});
|
});
|
||||||
|
|
||||||
observer.observe(document.body, { childList: true, subtree: true });
|
const grid = document.getElementById("modelGrid");
|
||||||
|
observer.observe(grid || document.body, { childList: true, subtree: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Init ───────────────────────────────────────────────────────
|
// ── Init ───────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -74,6 +74,8 @@ class StatsDB:
|
|||||||
|
|
||||||
def upsert_batch(self, rows: list[tuple[str, dict]]) -> None:
|
def upsert_batch(self, rows: list[tuple[str, dict]]) -> None:
|
||||||
"""Insert or update stats for multiple models."""
|
"""Insert or update stats for multiple models."""
|
||||||
|
if not rows:
|
||||||
|
return
|
||||||
conn = self._ensure_conn()
|
conn = self._ensure_conn()
|
||||||
now = time.time()
|
now = time.time()
|
||||||
conn.executemany(
|
conn.executemany(
|
||||||
|
|||||||
Reference in New Issue
Block a user