Skip to content

v1.12.2#763

Merged
ajslater merged 284 commits into
mainfrom
develop
May 11, 2026
Merged

v1.12.2#763
ajslater merged 284 commits into
mainfrom
develop

Conversation

@ajslater
Copy link
Copy Markdown
Owner

  • Fix
    • Ordering in browser card mode was broken for groups

ajslater added 30 commits April 5, 2026 17:11
* bump news

* cirlcle ci no longer handles pre-release

* remove alpha scripts for circleci"

* fix lint-ci

* fix pre-relase gha

* fix default last route on start pages

* bump version

* fix default params

* alpha2
* bump news

* ignore ruff qa for debug build

* ignore shellcheck"

* lint

* minor refactors of opds v2 feed
ajslater and others added 29 commits May 9, 2026 11:27
…cker (#751)

Previously the column picker always appended newly-toggled columns to
the end of the order list. This adds canonical-rank-aware insertion: if
the existing draft is in strictly-increasing canonical order (the order
implicit in `_CATEGORIES`), splice the new column into the unique slot
that keeps the sequence sorted. If the user has manually rearranged the
draft out of canonical order, fall back to appending so we don't reshuffle
their layout.

Examples (against the default "p" group `[cover, name, child_count]`):
  toggle favorite  → [cover, favorite, name, child_count]
  toggle imprint   → [cover, imprint_name, name, child_count]
  toggle publisher → [cover, publisher_name, name, child_count]

Covered by 16 new pure-function vitest cases.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
BROWSER_CHOICES is dumped to both browser-choices.json (Vuetify list)
and browser-map.json (raw map), but each format is consumed
selectively by the frontend. Add per-file include-key frozensets in
codex/choices/browser.py and look them up from choices_to_json.py so
the generator skips keys the frontend never reads.

Removed orphans:
- browser-choices.json: IDENTIFIER_SOURCES (frontend uses map form)
- browser-map.json: BOOKMARK_FILTER, VUETIFY_NULL_CODE, SETTINGS_GROUP
  (frontend uses Vuetify form)

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The `.*` glob in `[tool.codespell].skip` matched every walked path
because `os.walk('.')` prefixes paths with `./`, so codespell scanned
nothing during `make lint` while nvim per-file checks still flagged
typos. Replace `.*` with explicit hidden-dir paths, fix the
`/uv.lock` typo, and add legitimate technical terms (wan, crate, iff,
ser, etc.) to ignore-words-list.

Also wire codespell into the frontend lint script so frontend code
gets spell-checked too, and fix one real typo uncovered (secifies →
specifies in opds/v2/progression.py).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* audit and fix tool ignore/skip configs

- fix eslint **/*min.css and **/*min.js globs that over-matched (e.g. admin.css)
- fix [tool.vulture] test_results/ -> test-results/ typo
- move stray builtin = "clear,code,rare" into [tool.codespell]
- fix uv build-backend source-include typos (mkdocks.yml, .circlci/**) and drop dead entries (ci/**, top-level strange.jpg)
- remove duplicate "site" in [tool.basedpyright] exclude
- drop stale codex/_vendor references from all tool configs
- simplify [tool.complexipy] exclude (paths gating made entries unreachable)
- drop redundant entries from [tool.ruff] (dist already in defaults) and [tool.djlint] (covered by use_gitignore = true)
- normalize codespell skip prefixes; replace bare "coverage" with htmlcov + .coverage*; add *.svg, frontend/src/choices
- add coverage, htmlcov, .eslintcache to ESLint base ignores
- add comics/* and vulture_ignorelist.py to radon exclude
- normalize vulture *​/X* patterns to **/X so root-level matches work; align ty exclude with basedpyright
- delete unused [tool.typos] block

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* update deps

* fix remark for claude

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* bump version to alpha

* bump news for favorites
* Add optional failed-login log for fail2ban et al.

Adds a default-off feature that appends one parseable line per failed
credential attempt to a dedicated file, so banning tools (fail2ban,
CrowdSec, sshguard) can tail it via regex. Gated end-to-end on
`auth.failed_login_log` so disabled deployments pay zero overhead.

Hooks `django.contrib.auth.signals.user_login_failed`, which fires for
both rest_registration's form login and DRF's BasicAuthentication
(OPDS), covering both vectors with one receiver. rest_registration's
default authenticator drops `request=`, so a small contextvar
middleware stashes the active request to recover the client IP for
form logins. XFF trust is configurable for direct-exposed deployments
that can't trust the header.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Document failed-login log in README

Add env-var references in the Authentication subsection plus a new
"Failed-Login Log" section covering line format, XFF trust trade-off,
and a worked fail2ban filter + jail example.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A fresh browser visit to Codex probes /api/v3/auth/profile/ before any
session cookie exists, which Django's BaseHandler.get_response logs as
a WARNING ("Forbidden: /api/v3/auth/profile/") for every first-time
visitor. That drowns the main log in routine noise.

Add a logging.Filter on django.request that downgrades records matching
that exact "Forbidden: <path>" pattern to DEBUG, parameterized by a
small frozenset of known-noisy paths so abuse on other endpoints still
surfaces at WARNING.

Also move django.request into the loggers dict where dictConfig will
actually see it — the old top-level placement was silently ignored.
Records still flow to the root loguru handler via propagation.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add not_failed_login_filter and attach it to the stdout / codex.log
sinks so the IP-bearing line is written only to failed_logins.log.
Django's own request logger still emits a bare WARNING for each
failed attempt ("Unauthorized: /api/v3/auth/login/" etc.) so the
failure remains visible in codex.log without exposing the client IP.

Concentrating IP + username in one file makes the privacy story
easier to reason about: one file to chmod, forward to a SIEM, or
retain on its own schedule.

The inverse filter is only attached when the feature is enabled —
when disabled, no records carry the tag and the filter would be a
no-op anyway.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…n groups (#762)

The browser table-view PR (#745) routed group-row scalar / M2M sort
through ``scalar_intersection_sort_expr`` / ``m2m_intersection_sort_expr``
for every view mode. Intersection returns NULL when a group's children
disagree on the sorted field, which is correct for table view (sort
matches the intersection cell display) but blanks the order_value
caption beneath cover-mode cards for any group with mixed children.
The user reported this for Publish Date across publishers, series,
and folders.

Gate the intersection branch on ``view_mode == "table"`` so cover
mode falls back to the pre-table-view aggregate (Min/Max/Avg/Sum)
for scalars and ``sort_name`` for M2M.

Regression test reproduces the user's scenario: a "Mixed" series
with children at year=2018 and year=2024 returned orderValue=null
before; now returns "2018" (Min aggregate).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ajslater ajslater merged commit e18efcc into main May 11, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant