diff --git a/invenio.cfg b/invenio.cfg index 4ce0400c..0c8a8ae6 100644 --- a/invenio.cfg +++ b/invenio.cfg @@ -73,6 +73,7 @@ from zenodo_rdm.custom_fields import ( NAMESPACES, ) from zenodo_rdm.custom_schemes import is_edmo +from zenodo_rdm.db import routed_bind from zenodo_rdm.files import storage_factory from zenodo_rdm.github.schemas import CitationMetadataSchema from zenodo_rdm.legacy.resources import record_serializers @@ -149,6 +150,114 @@ if _parse_env_bool("INVENIO_PGBOUNCER_ENABLED", False): else: SQLALCHEMY_DATABASE_URI = "postgresql+psycopg2://zenodo:zenodo@localhost/zenodo" +# fmt: off +ZENODO_UI_READ_ONLY_ENDPOINTS = [ + "invenio_app_rdm.frontpage_view_function", # / + "invenio_app_rdm_records.record_from_pid", # // + "invenio_redirector.redirect_about", # /about + "invenio_formatter_badges.badge", # png):ext> + "invenio_github_badge.index", # /badge/.svg + "invenio_github_badge.index_old", # /badge//.svg + "invenio_github_badge.latest_doi", # /badge/latestdoi/ + "invenio_github_badge.latest_doi_old", # /badge/latestdoi// + "invenio_redirector.redirect_communities_search_legacy", # /collection/ + "invenio_redirector.redirect_collections_about", # /collection/user- + "invenio_communities.communities_frontpage", # /communities + "invenio_communities.communities_search", # /communities-search + "invenio_redirector.redirect_communities_curate", # /communities//curate + "invenio_redirector.redirect_communities_edt", # /communities//edit + "invenio_redirector.redirect_communities_search", # /communities//search + "invenio_app_rdm_communities.communities_home", # /communities// + "invenio_communities.communities_about", # /communities//about + "invenio_app_rdm_communities.communities_browse", # /communities//browse + "invenio_communities.communities_subcommunities", # /communities//browse/subcommunities + "invenio_app_rdm_communities.community_collection", # /communities//collections// + "invenio_communities.community_theme_css_config", # /communities//community-theme-.css + "invenio_communities.communities_curation_policy", # /communities//curation-policy + "invenio_communities.members", # /communities//members + "invenio_app_rdm_communities.community_static_page", # /communities//pages/ + "invenio_app_rdm_communities.communities_detail", # /communities//records + "invenio_redirector.redirect_communities_about_legacy", # /communities/about/ + "invenio_communities.deprecated_communities_search", # /communities/search + "invenio_redirector.redirect_contact", # /contact + "invenio_redirector.redirect_dev", # /dev + "invenio_redirector.redirect_donate", # /donate + "invenio_redirector.redirect_faq", # /faq + "invenio_redirector.redirect_features", # /features + "invenio_app_rdm.help_search", # /help/search + "invenio_app_rdm.help_statistics", # /help/statistics + "invenio_app_rdm.help_versioning", # /help/versioning + "invenio_redirector.redirect_policies", # /policies + "invenio_redirector.redirect_privacy-policy", # /privacy-policy + "invenio_redirector.redirect_record_detail", # /record/ + "invenio_redirector.redirect_record_export", # /record//export/ + "invenio_redirector.redirect_record_file_download", # /record//files/ + "invenio_redirector.redirect_formats_to_media_files", # /record//formats + "invenio_redirector.redirect_record_file_preview", # /record//preview/ + "invenio_redirector.redirect_record_thumbnail", # /record//thumb + "invenio_app_rdm_records.record_detail", # /records/ + "invenio_app_rdm_records.record_export", # /records//export/ + "invenio_redirector.redirect_legacy_record_export_view_dcat", # /records//export/dcat + "invenio_redirector.redirect_legacy_record_export_view_dcite4", # /records//export/dcite4 + "invenio_redirector.redirect_legacy_record_export_view_hx", # /records//export/hx + "invenio_redirector.redirect_legacy_record_export_view_xd", # /records//export/xd + "invenio_redirector.redirect_legacy_record_export_view_xm", # /records//export/xm + "invenio_app_rdm_records.record_file_download", # /records//files/ + "invenio_app_rdm_records.record_latest", # /records//latest + "invenio_app_rdm_records.record_media_file_download", # /records//media-files/ + "invenio_app_rdm_records.record_file_preview", # /records//preview/ + "invenio_app_rdm_records.record_thumbnail", # /records//thumb + "invenio_search_ui.search", # /search + "invenio_redirector.redirect_terms", # /terms +] + +ZENODO_API_READ_ONLY_ENDPOINTS = [ + "collections.search_records", # /collections//records + "communities.search", # /communities + "communities.read", # /communities/ + "communities.featured_list", # /communities//featured + "communities.read_logo", # /communities//logo + "community_members.search", # /communities//members + "community_members.search_public", # /communities//members/public + "community-records.search", # /communities//records + "communities.search_subcommunities", # /communities//subcommunities + "communities.featured_communities_search", # /communities/featured + "exporter.list_object_versions", # /exporter + "exporter.get_object_version_content", # /exporter/ + "exporter.get_object_version_content", # /exporter// + "iiif.base", # /iiif/ + "iiif.image_api", # /iiif/////. + "iiif.canvas", # /iiif//canvas/ + "iiif.info", # /iiif//info.json + "iiif.manifest", # /iiif//manifest + "iiif.sequence", # /iiif//sequence/default + "records.search", # /records + "zenodo_api_redirector.redirect_records_search_slash", # /records/ + "records.read", # /records/ + "record_files.search", # /records//files + "record_files.read_archive", # /records//files-archive + "record_files.read", # /records//files/ + "record_files.read_content", # /records//files//content + "record_media_files.search", # /records//media-files + "record_media_files.read_archive", # /records//media-files-archive + "record_media_files.read", # /records//media-files/ + "record_media_files.read_content", # /records//media-files//content + "records.search_versions", # /records//versions + "records.read_latest", # /records//versions/latest +] +# fmt: on + +ZENODO_READ_REPLICA_ENDPOINTS = ( + ZENODO_UI_READ_ONLY_ENDPOINTS + ZENODO_API_READ_ONLY_ENDPOINTS +) +if IS_LOCAL_DEV: + SQLALCHEMY_BINDS = { + "read_replica": "postgresql+psycopg2://zenodo_ro:zenodo_ro@localhost/zenodo" + } + + DB_SESSION_BIND_FUNC = routed_bind + + # Invenio-App # =========== # See https://invenio-app.readthedocs.io/en/latest/configuration.html diff --git a/site/zenodo_rdm/db.py b/site/zenodo_rdm/db.py new file mode 100644 index 00000000..edeb03e7 --- /dev/null +++ b/site/zenodo_rdm/db.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2025 CERN. +# +# ZenodoRDM is free software; you can redistribute it and/or modify it +# under the terms of the MIT License; see LICENSE file for more details. +"""Database helpers.""" + +from flask import current_app, request +from flask_login import current_user + + +def routed_bind(session, *args, **kwargs): + """Route session to the appropriate database depending on the request context. + + Routes unauthenticated/anonymous GET and HEAD requests of configured endpoints to + the configured read replica SQLAlchemy bind. + """ + if request and request.method in ["GET", "HEAD"]: + read_endpoints = current_app.config.get("ZENODO_READ_REPLICA_ENDPOINTS", []) + if current_user.is_anonymous and request.endpoint in read_endpoints: + return session._db.engines["read_replica"]