From 62026ae9ac7cd0d14cbfb278cbba469bc7aa1d56 Mon Sep 17 00:00:00 2001 From: Niels Rogge Date: Tue, 3 Nov 2026 10:16:26 +0000 Subject: [PATCH] Redirect merged author pages to the author they were merged into /authors/ is rendered server-side straight from the authors table, so an author ID merged away through author_aliases (e.g. /authors/251936 into /authors/181986) returned "Page not found", although the authors API already resolves aliases. The page route now 301-redirects a merged ID to the surviving author, keeping the query string, before the organization redirect. Co-Authored-By: Claude Opus 5.5 --- backend/routers/discovery_pages.py | 12 ++++++++---- backend/services/author_service.py | 16 ++++++++++------ backend/tests/test_search_discovery.py | 13 +++++++++++++ 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/backend/routers/discovery_pages.py b/backend/routers/discovery_pages.py index b088d994..7f13b183 100644 --- a/backend/routers/discovery_pages.py +++ b/backend/routers/discovery_pages.py @@ -10,7 +10,10 @@ from backend.config import get_settings from backend.database.connection import get_database_session -from backend.services.author_service import organization_slug_for_author +from backend.services.author_service import ( + merged_author_id, + organization_slug_for_author, +) from backend.services.content_negotiation_service import ( prefers_markdown, public_page_headers, @@ -222,9 +225,10 @@ def conference_task_page( def author_page( author_id: int, request: Request, db: Session = Depends(get_database_session) ) -> Response: - org_slug = organization_slug_for_author(db, author_id) - if org_slug: - target = f"/orgs/{org_slug}" + merged_into = merged_author_id(db, author_id) + org_slug = None if merged_into else organization_slug_for_author(db, author_id) + if merged_into or org_slug: + target = f"/authors/{merged_into}" if merged_into else f"/orgs/{org_slug}" if request.url.query: target = f"{target}?{request.url.query}" return RedirectResponse(target, status_code=301) diff --git a/backend/services/author_service.py b/backend/services/author_service.py index 7b65b7d7..f3ece9a8 100644 --- a/backend/services/author_service.py +++ b/backend/services/author_service.py @@ -97,6 +97,15 @@ def get_or_create_author_by_name(db: Session, name: str) -> DBAuthor: return author +def merged_author_id(db: Session, author_id: int) -> Optional[int]: + """Return the author an ID was merged into, or None if it was not merged.""" + return ( + db.query(AuthorAlias.author_id) + .filter(AuthorAlias.alias_id == author_id) + .scalar() + ) + + class AuthorService: """Service class for author-related operations.""" @@ -106,12 +115,7 @@ def __init__(self, db: Session): def _resolve_author_id(self, author_id: str) -> int: """Map an author ID merged into another author to the surviving ID.""" requested_id = int(author_id) - merged_into = ( - self.db.query(AuthorAlias.author_id) - .filter(AuthorAlias.alias_id == requested_id) - .scalar() - ) - return merged_into or requested_id + return merged_author_id(self.db, requested_id) or requested_id def get_authors( self, diff --git a/backend/tests/test_search_discovery.py b/backend/tests/test_search_discovery.py index 4343b9b8..3f247a08 100644 --- a/backend/tests/test_search_discovery.py +++ b/backend/tests/test_search_discovery.py @@ -528,7 +528,19 @@ def test_merged_organization_slug_redirects_to_canonical_page(client): assert response.headers["location"] == "/orgs/google?q=vision" +def test_merged_author_redirects_to_the_author_it_was_merged_into(client, monkeypatch): + monkeypatch.setattr( + discovery_pages, + "merged_author_id", + lambda db, author_id: 181986 if author_id == 251936 else None, + ) + response = client.get("/authors/251936?has_code=true", follow_redirects=False) + assert response.status_code == 301 + assert response.headers["location"] == "/authors/181986?has_code=true" + + def test_author_naming_an_organization_redirects_to_its_page(client, monkeypatch): + monkeypatch.setattr(discovery_pages, "merged_author_id", lambda db, author_id: None) monkeypatch.setattr( discovery_pages, "organization_slug_for_author", @@ -556,6 +568,7 @@ def test_discovery_page_classes_support_head(client, monkeypatch, path, method_n monkeypatch.setattr( DiscoveryPageService, method_name, lambda self, *args, **kwargs: _page() ) + monkeypatch.setattr(discovery_pages, "merged_author_id", lambda db, author_id: None) monkeypatch.setattr( discovery_pages, "organization_slug_for_author", lambda db, author_id: None )