From 7ba492f4a25c8c14b9c1c6fa16010e853f6969b5 Mon Sep 17 00:00:00 2001 From: Eric Larson Date: Fri, 21 Aug 2026 10:35:12 -0500 Subject: [PATCH] Rename project identity to OpenGeoMetadata --- Makefile | 5 +- README.md | 25 +++- backend/app/api/ogc/endpoints.py | 18 +-- .../app/api/v1/endpoint_modules/analytics.py | 8 +- .../app/api/v1/endpoint_modules/gazetteer.py | 40 +++---- .../endpoint_modules/resources/thumbnail.py | 2 +- backend/app/api/v1/endpoint_modules/root.py | 10 +- backend/app/api/v1/endpoint_modules/search.py | 2 +- .../app/api/v1/endpoint_modules/shapefiles.py | 2 +- backend/app/api/v1/endpoint_modules/slack.py | 2 +- backend/app/api/v1/gazetteer.py | 70 +++++------ backend/app/api/v1/presenters/resource.py | 2 +- backend/app/api/v1/shapefiles.py | 2 +- backend/app/api/v1/utils.py | 2 +- backend/app/elasticsearch/client.py | 2 +- backend/app/elasticsearch/index.py | 4 +- backend/app/elasticsearch/mappings.py | 2 +- backend/app/elasticsearch/search.py | 10 +- backend/app/gazetteer/download.py | 2 +- backend/app/gazetteer/import_all.py | 18 +-- backend/app/gazetteer/importers/__init__.py | 4 +- .../{btaa_importer.py => ogm_importer.py} | 22 ++-- backend/app/main.py | 8 +- .../app/middleware/rate_limit_middleware.py | 6 +- .../app/middleware/turnstile_middleware.py | 4 +- backend/app/services/api_key_service.py | 40 ++++--- backend/app/services/api_usage_log_service.py | 8 +- backend/app/services/bridge_sync/report.py | 14 +-- .../app/services/bridge_sync/search_index.py | 2 +- .../app/services/citation_formats_service.py | 4 +- backend/app/services/citation_service.py | 6 +- .../app/services/distribution_repository.py | 2 +- backend/app/services/feedback_service.py | 8 +- backend/app/services/image_service.py | 2 +- backend/app/services/mcp_service.py | 8 +- backend/app/services/nominatim_service.py | 2 +- backend/app/services/ogc_projector.py | 6 +- backend/app/services/ogm_field_mapper.py | 10 +- backend/app/services/search_service.py | 2 +- backend/app/services/sitemap_service.py | 2 +- backend/app/services/slackbot_service.py | 24 ++-- backend/app/services/turnstile_service.py | 2 +- backend/app/tasks/worker.py | 6 +- backend/conftest.py | 14 +-- .../data/fixtures/download_b1g_fixtures.py | 6 +- ...a_fixtures.py => download_ogm_fixtures.py} | 6 +- .../ee251fd05e504374831ab4ddf5e589f2_2.json | 0 .../018b1db0-726a-4727-af0a-5c7e18783ace.json | 0 .../09a-04.json | 0 .../0b75937e-2f44-4e49-bd1e-3e3adbed6f84.json | 0 .../14E37141-2059-462E-ABCA-9628E3BFB636.json | 0 .../16465B6B-742A-4335-BBF5-C4F7EC1BA9D4.json | 0 .../219ffed3-3e58-4fb7-ad82-4c264aae1b17.json | 0 ...19ffed3-3e58-4fb7-ad82-4c264aae1b17_1.json | 0 .../2787a9e8-0bef-452e-9e5f-e83042c193b4.json | 0 .../4979dd07507f4155bb92689860dd5089.json | 0 .../4B758FE6-D2B5-463D-8E25-502CB4D90376.json | 0 .../4cd9f01beba64dce9e8502a7924d0deb_0.json | 0 .../502D1D34-FDB0-456E-BD4A-73299B9C2E5F.json | 0 .../8888-002.json | 0 .../88be737b-4fea-4f23-9433-a008ed6b18b5.json | 0 .../999-0003-007.json | 0 .../999-0011-california.json | 0 .../9eccb622-8fe3-4f94-9a5c-e166585eb597.json | 0 ...esha-3eea70a5e4af40a1a558a43705ff8596.json | 0 .../VAC9619-000022.json | 0 .../b1e04fea-8a02-426d-94c1-0897707fa563.json | 0 .../b1g_BtbnzIbFhMiC.json | 0 .../b1g_Jeks5eSaDHp5.json | 0 .../b1g_PJxxfKgpqpUT.json | 0 .../cugir-007739.json | 0 .../cugir-007739_1.json | 0 .../d128aa1618744699be00be0e494d01de_0.json | 0 .../p16022coll230_2937.json | 0 .../p16022coll230_3666.json | 0 .../p16022coll230_3666_1.json | 0 .../p16022coll231_2412.json | 0 ...pstems_0052767067_brownsville_08_pitt.json | 0 .../rutgers-lib_35507.json | 0 .../stanford-bs024ty5255.json | 0 .../utaustin_121171.html | 0 ...patial_api.txt => ogm_api_legacy_dump.txt} | 0 backend/data/parade_db_gbl_table.py | 2 +- backend/db/config.py | 10 +- backend/db/create_tables.py | 2 +- .../db/migrations/add_api_keys_allowed_ips.py | 7 +- backend/db/migrations/add_enrichment_type.py | 2 +- ...lds.py => add_latest_ogm_schema_fields.py} | 20 ++-- .../add_missing_fields_for_migration.py | 4 +- ...d_btaa_ogm_fields.py => add_ogm_fields.py} | 20 ++-- .../backfill_resources_from_legacy_items.py | 2 +- .../db/migrations/bridge_old_production.py | 6 +- .../db/migrations/create_ai_enrichments.py | 2 +- .../create_api_rate_limiting_tables.py | 6 +- .../migrations/create_bridge_sync_tables.py | 2 +- .../create_data_dictionary_tables.py | 2 +- .../migrations/create_distribution_tables.py | 2 +- .../create_distribution_types_table.py | 2 +- .../db/migrations/create_gazetteer_tables.py | 2 +- .../create_generated_api_responses_table.py | 2 +- ...enerated_resource_representations_table.py | 2 +- .../create_generated_visual_assets_table.py | 2 +- .../migrations/create_gin_blog_posts_table.py | 2 +- .../migrations/create_item_relationships.py | 2 +- .../migrations/create_ogm_harvest_tables.py | 2 +- .../migrations/create_resource_aux_tables.py | 2 +- .../create_resource_distributions_table.py | 2 +- .../create_resource_relationships.py | 2 +- .../create_resource_spatial_facets_table.py | 2 +- .../create_resource_thumbnail_state_table.py | 2 +- .../migrations/import_from_old_production.py | 2 +- backend/db/migrations/initialize_api_tiers.py | 111 ++++++++++++------ .../migrate_document_distributions.py | 2 +- .../migrate_resource_data_dictionaries.py | 2 +- .../optimize_spatial_facet_indexing.py | 2 +- .../db/migrations/optimize_spatial_queries.py | 2 +- .../populate_resource_distributions.py | 2 +- .../db/migrations/remove_geocoding_columns.py | 8 +- .../db/migrations/rename_all_item_tables.py | 2 +- ..._usage_logs_to_analytics_api_usage_logs.py | 6 +- .../rename_friendlier_id_to_resource_id.py | 2 +- backend/db/migrations/rename_indexes.py | 2 +- .../rename_item_id_to_resource_id.py | 2 +- .../migrations/rename_items_to_resources.py | 2 +- ...olumns.py => rename_legacy_ogm_columns.py} | 12 +- .../rename_remaining_constraints.py | 2 +- .../rollback_spatial_optimizations.py | 2 +- .../sync_old_production_references.py | 4 +- .../test_distribution_migrations.py | 2 +- .../db/migrations/update_ogm_field_names.py | 6 +- .../update_spatial_facets_for_wof_ids.py | 2 +- backend/db/models.py | 12 +- backend/examples/advanced_queries_example.py | 2 +- backend/examples/facet_search_example.py | 2 +- .../examples/geo_polygon_search_example.py | 2 +- .../examples/spatial_search_bbox_example.py | 2 +- .../spatial_search_distance_example.py | 2 +- ...spatial_search_polygon_relation_example.py | 2 +- .../spatial_search_shape_envelope_example.py | 2 +- backend/scripts/backup_elasticsearch.py | 4 +- backend/scripts/backup_postgres_to_s3.py | 6 +- .../scripts/bootstrap_kamal_deploy_user.sh | 6 +- backend/scripts/check_elasticsearch_health.py | 2 +- backend/scripts/check_mapping.py | 4 +- backend/scripts/clone_db_for_tests.sh | 10 +- backend/scripts/debug_county_query.py | 2 +- .../scripts/debug_index_missing_resources.py | 2 +- backend/scripts/debug_static_map.py | 2 +- backend/scripts/diagnose_index_failures.py | 2 +- backend/scripts/diagnose_missing_resources.py | 2 +- ...taa_fixtures.py => ingest_ogm_fixtures.py} | 14 +-- backend/scripts/load_fixtures.py | 2 +- backend/scripts/monitor_reindex.py | 2 +- backend/scripts/ogm_importer.py | 4 +- backend/scripts/populate_distributions.py | 2 +- backend/scripts/populate_ogm_repos.py | 4 +- backend/scripts/prime_thumbnail_cache.py | 2 +- backend/scripts/reindex.py | 4 +- backend/scripts/reindex_atomic.py | 2 +- backend/scripts/run_btaa_migration.py | 31 ----- backend/scripts/run_gazetteers.py | 8 +- backend/scripts/run_index.py | 2 +- backend/scripts/run_ogm_migration.py | 31 +++++ backend/scripts/run_tests_with_real_data.sh | 4 +- backend/scripts/simple_bulk_index.py | 2 +- backend/scripts/start_web_singlehost.sh | 16 +-- backend/scripts/test_gazetteer_api.py | 10 +- .../validate_elasticsearch_production.py | 2 +- backend/scripts/validate_spatial_facets.py | 2 +- backend/scripts/verify_h3_index.py | 2 +- backend/static/btaa-gin-white.png | Bin 30416 -> 0 bytes backend/static/btaa-logo-white.png | Bin 14862 -> 0 bytes backend/templates/docs.html | 4 +- backend/tests/README.md | 4 +- .../tests/api/test_analytics_cli_events.py | 10 +- backend/tests/api/test_error_contracts.py | 2 +- backend/tests/api/test_ogc.py | 14 +-- backend/tests/api/v1/test_analytics_events.py | 10 +- .../test_endpoint_modules_gazetteer_extra.py | 18 +-- ...t_endpoint_modules_gazetteer_production.py | 42 +++---- ...test_endpoint_modules_shapefiles_simple.py | 2 +- backend/tests/api/v1/test_gazetteer.py | 36 +++--- .../tests/api/v1/test_gazetteer_endpoints.py | 54 ++++----- backend/tests/api/v1/test_home_blog_posts.py | 4 +- .../tests/api/v1/test_resource_presenter.py | 6 +- .../v1/test_resource_thumbnail_endpoints.py | 2 +- backend/tests/api/v1/test_slack_endpoints.py | 2 +- .../tests/api/v1/test_turnstile_endpoints.py | 2 +- backend/tests/db/test_async_engine.py | 8 +- backend/tests/elasticsearch/test_mappings.py | 12 +- backend/tests/elasticsearch/test_search.py | 6 +- backend/tests/gazetteer/test_import_all.py | 4 +- .../test_mkdocs_interactive_examples.py | 6 +- .../middleware/test_rate_limit_integration.py | 4 +- .../middleware/test_rate_limit_middleware.py | 6 +- .../middleware/test_turnstile_middleware.py | 34 +++--- .../scripts/test_backup_elasticsearch.py | 4 +- .../scripts/test_backup_postgres_to_s3.py | 14 +-- backend/tests/scripts/test_render_cron_env.py | 4 +- .../tests/services/test_api_key_service.py | 79 ++++++++++++- .../services/test_api_usage_log_service.py | 18 +-- .../test_api_usage_log_service_cli.py | 14 +-- .../services/test_bridge_search_index.py | 4 +- .../services/test_bridge_sync_service.py | 2 +- .../services/test_citation_formats_service.py | 6 +- .../tests/services/test_feedback_service.py | 2 +- .../tests/services/test_ogm_field_mapper.py | 10 +- .../tests/services/test_rate_limit_service.py | 4 +- backend/tests/services/test_search_service.py | 12 +- .../test_worker_fetch_and_cache_image.py | 2 +- backend/tests/test_ogm_field_mapper.py | 10 +- config/geobtaa-backend-source.env | 15 --- config/ogm-owned-paths.txt | 2 +- config/upstream-backend-source.env | 15 +++ docs/backend_upstream_sync.md | 4 +- docs/gazetteer_api.md | 34 +++--- docs/gazetteer_data_management.md | 16 +-- docs/repository_branch_inventory.md | 4 +- docs/repository_transfer_rehearsal.md | 4 +- docs/scripts.md | 2 +- docs/upstream_reconciliation.md | 50 ++++---- legacy/root_api/app/elasticsearch/client.py | 2 +- legacy/root_api/app/elasticsearch/index.py | 4 +- legacy/root_api/app/elasticsearch/search.py | 2 +- legacy/root_api/app/gazetteer/download.py | 2 +- legacy/root_api/app/gazetteer/import_all.py | 18 +-- .../app/gazetteer/importers/__init__.py | 4 +- .../{btaa_importer.py => ogm_importer.py} | 22 ++-- .../root_api/app/services/search_service.py | 2 +- legacy/root_api/db/config.py | 2 +- legacy/root_api/db/create_tables.py | 2 +- .../db/migrations/create_gazetteer_tables.py | 16 +-- legacy/root_api/db/models.py | 6 +- ...a_ogm_api.sql.gz => ogm_api_legacy.sql.gz} | Bin ...aa_ogm_api.txt => ogm_api_legacy_dump.txt} | 0 legacy/root_api/parade_db_gbl_table.py | 2 +- legacy/root_api/scripts/process_allmaps.py | 2 +- legacy/root_api/scripts/test_gazetteer_api.py | 10 +- legacy/root_api/tests/README.md | 4 +- legacy/root_api/tests/api/v1/conftest.py | 4 +- .../tests/api/v1/test_jsonapi_structure.py | 2 +- .../tests/api/v1/test_resource_endpoints.py | 2 +- scripts/sync_backend_from_data_api.sh | 28 ++--- scripts/verify_identity_cleanup.sh | 34 ++++++ scripts/verify_transfer_readiness.sh | 8 +- 245 files changed, 966 insertions(+), 787 deletions(-) rename backend/app/gazetteer/importers/{btaa_importer.py => ogm_importer.py} (92%) rename backend/data/fixtures/{download_btaa_fixtures.py => download_ogm_fixtures.py} (96%) rename backend/data/fixtures/{btaa_featured_resources => ogm_featured_resources}/ee251fd05e504374831ab4ddf5e589f2_2.json (100%) rename backend/data/fixtures/{btaa_fixtures_data => ogm_fixtures_data}/018b1db0-726a-4727-af0a-5c7e18783ace.json (100%) rename backend/data/fixtures/{btaa_fixtures_data => ogm_fixtures_data}/09a-04.json (100%) rename backend/data/fixtures/{btaa_fixtures_data => ogm_fixtures_data}/0b75937e-2f44-4e49-bd1e-3e3adbed6f84.json (100%) rename backend/data/fixtures/{btaa_fixtures_data => ogm_fixtures_data}/14E37141-2059-462E-ABCA-9628E3BFB636.json (100%) rename backend/data/fixtures/{btaa_fixtures_data => ogm_fixtures_data}/16465B6B-742A-4335-BBF5-C4F7EC1BA9D4.json (100%) rename backend/data/fixtures/{btaa_fixtures_data => ogm_fixtures_data}/219ffed3-3e58-4fb7-ad82-4c264aae1b17.json (100%) rename backend/data/fixtures/{btaa_fixtures_data => ogm_fixtures_data}/219ffed3-3e58-4fb7-ad82-4c264aae1b17_1.json (100%) rename backend/data/fixtures/{btaa_fixtures_data => ogm_fixtures_data}/2787a9e8-0bef-452e-9e5f-e83042c193b4.json (100%) rename backend/data/fixtures/{btaa_fixtures_data => ogm_fixtures_data}/4979dd07507f4155bb92689860dd5089.json (100%) rename backend/data/fixtures/{btaa_fixtures_data => ogm_fixtures_data}/4B758FE6-D2B5-463D-8E25-502CB4D90376.json (100%) rename backend/data/fixtures/{btaa_fixtures_data => ogm_fixtures_data}/4cd9f01beba64dce9e8502a7924d0deb_0.json (100%) rename backend/data/fixtures/{btaa_fixtures_data => ogm_fixtures_data}/502D1D34-FDB0-456E-BD4A-73299B9C2E5F.json (100%) rename backend/data/fixtures/{btaa_fixtures_data => ogm_fixtures_data}/8888-002.json (100%) rename backend/data/fixtures/{btaa_fixtures_data => ogm_fixtures_data}/88be737b-4fea-4f23-9433-a008ed6b18b5.json (100%) rename backend/data/fixtures/{btaa_fixtures_data => ogm_fixtures_data}/999-0003-007.json (100%) rename backend/data/fixtures/{btaa_fixtures_data => ogm_fixtures_data}/999-0011-california.json (100%) rename backend/data/fixtures/{btaa_fixtures_data => ogm_fixtures_data}/9eccb622-8fe3-4f94-9a5c-e166585eb597.json (100%) rename backend/data/fixtures/{btaa_fixtures_data => ogm_fixtures_data}/CityOfWaukesha-3eea70a5e4af40a1a558a43705ff8596.json (100%) rename backend/data/fixtures/{btaa_fixtures_data => ogm_fixtures_data}/VAC9619-000022.json (100%) rename backend/data/fixtures/{btaa_fixtures_data => ogm_fixtures_data}/b1e04fea-8a02-426d-94c1-0897707fa563.json (100%) rename backend/data/fixtures/{btaa_fixtures_data => ogm_fixtures_data}/b1g_BtbnzIbFhMiC.json (100%) rename backend/data/fixtures/{btaa_fixtures_data => ogm_fixtures_data}/b1g_Jeks5eSaDHp5.json (100%) rename backend/data/fixtures/{btaa_fixtures_data => ogm_fixtures_data}/b1g_PJxxfKgpqpUT.json (100%) rename backend/data/fixtures/{btaa_fixtures_data => ogm_fixtures_data}/cugir-007739.json (100%) rename backend/data/fixtures/{btaa_fixtures_data => ogm_fixtures_data}/cugir-007739_1.json (100%) rename backend/data/fixtures/{btaa_fixtures_data => ogm_fixtures_data}/d128aa1618744699be00be0e494d01de_0.json (100%) rename backend/data/fixtures/{btaa_fixtures_data => ogm_fixtures_data}/p16022coll230_2937.json (100%) rename backend/data/fixtures/{btaa_fixtures_data => ogm_fixtures_data}/p16022coll230_3666.json (100%) rename backend/data/fixtures/{btaa_fixtures_data => ogm_fixtures_data}/p16022coll230_3666_1.json (100%) rename backend/data/fixtures/{btaa_fixtures_data => ogm_fixtures_data}/p16022coll231_2412.json (100%) rename backend/data/fixtures/{btaa_fixtures_data => ogm_fixtures_data}/pstems_0052767067_brownsville_08_pitt.json (100%) rename backend/data/fixtures/{btaa_fixtures_data => ogm_fixtures_data}/rutgers-lib_35507.json (100%) rename backend/data/fixtures/{btaa_fixtures_data => ogm_fixtures_data}/stanford-bs024ty5255.json (100%) rename backend/data/fixtures/{btaa_fixtures_data => ogm_fixtures_data}/utaustin_121171.html (100%) rename backend/data/{btaa_geospatial_api.txt => ogm_api_legacy_dump.txt} (100%) rename backend/db/migrations/{add_latest_btaa_schema_fields.py => add_latest_ogm_schema_fields.py} (81%) rename backend/db/migrations/{add_btaa_ogm_fields.py => add_ogm_fields.py} (85%) rename backend/db/migrations/{rename_btaa_columns.py => rename_legacy_ogm_columns.py} (90%) rename backend/scripts/{ingest_btaa_fixtures.py => ingest_ogm_fixtures.py} (89%) delete mode 100644 backend/scripts/run_btaa_migration.py create mode 100644 backend/scripts/run_ogm_migration.py delete mode 100644 backend/static/btaa-gin-white.png delete mode 100644 backend/static/btaa-logo-white.png delete mode 100644 config/geobtaa-backend-source.env create mode 100644 config/upstream-backend-source.env rename legacy/root_api/app/gazetteer/importers/{btaa_importer.py => ogm_importer.py} (92%) rename legacy/root_api/ogm/{btaa_ogm_api.sql.gz => ogm_api_legacy.sql.gz} (100%) rename legacy/root_api/{btaa_ogm_api.txt => ogm_api_legacy_dump.txt} (100%) create mode 100755 scripts/verify_identity_cleanup.sh diff --git a/Makefile b/Makefile index 7005384..2893602 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: lint format lint-check test test-fast test-no-coverage migrate reindex gazetteers ogm-nightly cache-prime cache-prime-background kamal-registry-login transfer-readiness transfer-readiness-full +.PHONY: lint format lint-check identity-check test test-fast test-no-coverage migrate reindex gazetteers ogm-nightly cache-prime cache-prime-background kamal-registry-login transfer-readiness transfer-readiness-full BACKEND_DIR = backend @@ -16,6 +16,9 @@ lint-check: cd $(BACKEND_DIR) && ruff format --check app tests scripts cd $(BACKEND_DIR) && ruff check app tests scripts +identity-check: + ./scripts/verify_identity_cleanup.sh + test: @echo "Running backend test suite..." cd $(BACKEND_DIR) && pytest --full-trace diff --git a/README.md b/README.md index b691b49..d4bcde5 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ OpenGeoMetadata API is a deployable search, harvest, and delivery service for public [OpenGeoMetadata](https://opengeometadata.org/) Aardvark records. It is being prepared as the reference API node for the proposed [OpenGeoMetadata API Mirror Network](https://github.com/OpenGeoMetadata/ogm-mirror-network). +The current reference node is live at +[ogm.geo4lib.app/api/docs](https://ogm.geo4lib.app/api/docs). > [!IMPORTANT] > The mirror-network proposal is a **Draft for Community Discussion** @@ -62,9 +64,24 @@ public `GET` and `HEAD` routes. The backend began from the `geobtaa/api` backend subtree and now carries an OpenGeoMetadata-owned harvesting, branding, deployment, and cache overlay. It is not a Git fork with mergeable history. See -[BTAA backend reconciliation](docs/upstream_reconciliation.md) for the pinned +[Upstream backend reconciliation](docs/upstream_reconciliation.md) for the pinned source baseline, selective-port decisions, and accepted upstream fixes. +## Identity compatibility invariants + +Product-facing names, routes, defaults, assets, and documentation use OGM or +OpenGeoMetadata. A small number of historical strings remain deliberately: + +- the production PostgreSQL database and populated gazetteer table retain + their physical names until they can be changed with separate, staged data + migrations; +- API keys created with the previous fallback hash salt remain valid and are + upgraded to the OGM salt after successful authentication; and +- real upstream repository, GIN, linked-data, asset, and fixture-provenance + URLs retain the names of the external resources they identify. + +Run `make identity-check` to reject new, unapproved legacy branding. + ## Local development Copy the environment template: @@ -154,10 +171,10 @@ Redis. ## Releases and upstream provenance -OGM product versions and BTAA backend provenance are separate: +OGM product versions and upstream backend provenance are separate: - the product version describes this repository's API contract and release; -- `config/geobtaa-backend-source.env` records the last complete BTAA subtree +- `config/upstream-backend-source.env` records the last complete upstream subtree import; and - `docs/upstream_reconciliation.md` records selective ports made after that import. @@ -190,7 +207,7 @@ Read these before an operational change: - [Repository transfer runbook](docs/repository_transfer.md) - [Repository transfer rehearsal](docs/repository_transfer_rehearsal.md) - [Pre-transfer branch inventory](docs/repository_branch_inventory.md) -- [BTAA backend reconciliation](docs/upstream_reconciliation.md) +- [Upstream backend reconciliation](docs/upstream_reconciliation.md) - [Backend sync design](docs/backend_upstream_sync.md) Never change the Kamal service name, persistent volume paths, repository owner, diff --git a/backend/app/api/ogc/endpoints.py b/backend/app/api/ogc/endpoints.py index eed49fc..7eebd6a 100644 --- a/backend/app/api/ogc/endpoints.py +++ b/backend/app/api/ogc/endpoints.py @@ -86,17 +86,17 @@ async def get_collections(request: Request) -> Dict[str, Any]: @router.get( - "/collections/btaa-records", + "/collections/ogm-records", response_model=OGCCollectionResponse, responses=PUBLIC_ERROR_RESPONSES, ) async def get_collection(request: Request) -> Dict[str, Any]: url = str(request.url) - return OGCResponseProjector.build_collection(url, "btaa-records") + return OGCResponseProjector.build_collection(url, "ogm-records") @router.get( - "/collections/btaa-records/queryables", + "/collections/ogm-records/queryables", response_model=OGCQueryablesResponse, responses=PUBLIC_ERROR_RESPONSES, ) @@ -106,7 +106,7 @@ async def get_queryables(request: Request) -> Dict[str, Any]: @router.get( - "/collections/btaa-records/sortables", + "/collections/ogm-records/sortables", response_model=OGCSortablesResponse, responses=PUBLIC_ERROR_RESPONSES, ) @@ -116,7 +116,7 @@ async def get_sortables(request: Request) -> Dict[str, Any]: @router.get( - "/collections/btaa-records/items", + "/collections/ogm-records/items", response_model=OGCFeatureCollectionResponse, responses=PUBLIC_ERROR_RESPONSES, ) @@ -142,7 +142,7 @@ async def get_items( limit=limit, sort=internal_sort, include_filters=include_filters, - exclude_filters={}, # Empty dict to avoid parsing query params as BTAA filters + exclude_filters={}, # Empty dict to avoid parsing query params as OGM filters request_query_params=None, ) @@ -150,11 +150,11 @@ async def get_items( logger.error("OGC search request failed in search service") raise HTTPException(status_code=503, detail="Elasticsearch search failed") - return OGCResponseProjector.build_items_response(url, results, page, limit, "btaa-records") + return OGCResponseProjector.build_items_response(url, results, page, limit, "ogm-records") @router.get( - "/collections/btaa-records/items/{recordId}", + "/collections/ogm-records/items/{recordId}", response_model=OGCFeatureResponse, responses=PUBLIC_ERROR_RESPONSES, ) @@ -179,4 +179,4 @@ async def get_item( if not resource: raise HTTPException(status_code=404, detail="Resource not found") - return OGCResponseProjector.build_item(url, resource, "btaa-records") + return OGCResponseProjector.build_item(url, resource, "ogm-records") diff --git a/backend/app/api/v1/endpoint_modules/analytics.py b/backend/app/api/v1/endpoint_modules/analytics.py index 1d45454..562ad08 100644 --- a/backend/app/api/v1/endpoint_modules/analytics.py +++ b/backend/app/api/v1/endpoint_modules/analytics.py @@ -31,10 +31,10 @@ def _request_defaults(request: Request) -> Dict[str, Any]: headers = request.headers return { "visit_token": headers.get("X-Visit-Token"), - "client_name": headers.get("X-BTAA-Client-Name"), - "client_version": headers.get("X-BTAA-Client-Version"), - "client_channel": headers.get("X-BTAA-Client-Channel"), - "client_instance": headers.get("X-BTAA-Client-Instance"), + "client_name": headers.get("X-OGM-Client-Name"), + "client_version": headers.get("X-OGM-Client-Version"), + "client_channel": headers.get("X-OGM-Client-Channel"), + "client_instance": headers.get("X-OGM-Client-Instance"), "source_host": _extract_source_host( headers.get("Origin"), headers.get("Referer"), diff --git a/backend/app/api/v1/endpoint_modules/gazetteer.py b/backend/app/api/v1/endpoint_modules/gazetteer.py index dad8908..b63c6c7 100644 --- a/backend/app/api/v1/endpoint_modules/gazetteer.py +++ b/backend/app/api/v1/endpoint_modules/gazetteer.py @@ -26,8 +26,8 @@ ) from db.database import database from db.models import ( - gazetteer_btaa, gazetteer_geonames, + gazetteer_ogm, gazetteer_wof_ancestors, gazetteer_wof_concordances, gazetteer_wof_geojson, @@ -62,7 +62,7 @@ async def list_gazetteers( select(func.count()).select_from(gazetteer_wof_spr) ) - btaa_count = await database.fetch_val(select(func.count()).select_from(gazetteer_btaa)) + ogm_count = await database.fetch_val(select(func.count()).select_from(gazetteer_ogm)) # Additional WOF table counts wof_ancestors_count = await database.fetch_val( @@ -110,13 +110,13 @@ async def list_gazetteers( }, }, { - "id": "btaa", + "id": "ogm", "type": "gazetteer", "attributes": { - "name": "BTAA", - "description": "Big Ten Academic Alliance Geoportal gazetteer", - "record_count": btaa_count or 0, - "website": "https://geo.btaa.org/", + "name": "OGM", + "description": "OpenGeoMetadata API gazetteer", + "record_count": ogm_count or 0, + "website": "https://opengeometadata.org/", }, }, ] @@ -147,8 +147,8 @@ async def search_all_gazetteers( return await search_geonames(request, q, limit, offset) elif gazetteer == "wof": return await search_wof(request, q, limit, offset) - elif gazetteer == "btaa": - return await search_btaa(request, q, limit, offset) + elif gazetteer == "ogm": + return await search_ogm(request, q, limit, offset) else: raise HTTPException(status_code=400, detail="Invalid gazetteer specified") @@ -156,7 +156,7 @@ async def search_all_gazetteers( results = {} results["geonames"] = await search_geonames(request, q, limit, offset) results["wof"] = await search_wof(request, q, limit, offset) - results["btaa"] = await search_btaa(request, q, limit, offset) + results["ogm"] = await search_ogm(request, q, limit, offset) # Extract data from JSONResponse objects for the combined response combined_results = {} @@ -214,15 +214,15 @@ async def search_nominatim( raise HTTPException(status_code=502, detail="Nominatim request failed") from exc -@router.get("/gazetteers/btaa/search", response_model=JSONAPIResponse) +@router.get("/gazetteers/ogm/search", response_model=JSONAPIResponse) @cached_endpoint(ttl=GAZETTEER_CACHE_TTL) -async def search_btaa( +async def search_ogm( request: Request, q: str = Query(..., description="Search query"), limit: int = Query(10, description="Maximum number of results", ge=1, le=100), offset: int = Query(0, description="Number of results to skip", ge=0), ): - """Search BTAA gazetteer.""" + """Search OGM gazetteer.""" try: # Build search query search_terms = q.split() @@ -231,14 +231,14 @@ async def search_btaa( for term in search_terms: conditions.append( or_( - gazetteer_btaa.c.fast_area.ilike(f"%{term}%"), + gazetteer_ogm.c.fast_area.ilike(f"%{term}%"), ) ) query = ( - select(gazetteer_btaa) + select(gazetteer_ogm) .where(and_(*conditions)) - .order_by(gazetteer_btaa.c.fast_area) + .order_by(gazetteer_ogm.c.fast_area) .limit(limit) .offset(offset) ) @@ -255,14 +255,14 @@ async def search_btaa( # Format as JSON:API resource formatted_row = { "id": str(row_dict.get("id", "")), - "type": "btaa", + "type": "ogm", "attributes": row_dict, } data.append(formatted_row) # Create meta and links using utility function with strong parameters meta, links = create_gazetteer_meta_and_links( - request, q, limit, offset, len(data), "btaa", allowed_params=GAZETTEER_ALLOWED_PARAMS + request, q, limit, offset, len(data), "ogm", allowed_params=GAZETTEER_ALLOWED_PARAMS ) # Create JSON:API compliant response @@ -284,8 +284,8 @@ async def search_btaa( return JSONResponse(content=reordered_response) except Exception as e: - logger.error(f"Error searching BTAA: {str(e)}", exc_info=True) - raise HTTPException(status_code=500, detail="Failed to search BTAA") from e + logger.error(f"Error searching OGM: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail="Failed to search OGM") from e @router.get("/gazetteers/geonames/search", response_model=JSONAPIResponse) diff --git a/backend/app/api/v1/endpoint_modules/resources/thumbnail.py b/backend/app/api/v1/endpoint_modules/resources/thumbnail.py index aedc056..02423de 100644 --- a/backend/app/api/v1/endpoint_modules/resources/thumbnail.py +++ b/backend/app/api/v1/endpoint_modules/resources/thumbnail.py @@ -121,7 +121,7 @@ async def _probe_thumbnail_url(url: str) -> bool: Used to avoid serving a queued-thumbnail fallback when the source is 404 or invalid. """ try: - headers = {"User-Agent": "BTAA-Geospatial-Data-API/1.0 (https://geo.btaa.org/)"} + headers = {"User-Agent": "OpenGeoMetadata-API/1.0 (https://opengeometadata.org/)"} timeout = aiohttp.ClientTimeout(total=THUMBNAIL_PROBE_TIMEOUT) async with aiohttp.ClientSession(timeout=timeout) as session: async with session.get(url, headers=headers) as resp: diff --git a/backend/app/api/v1/endpoint_modules/root.py b/backend/app/api/v1/endpoint_modules/root.py index 1ee7319..46b4e39 100644 --- a/backend/app/api/v1/endpoint_modules/root.py +++ b/backend/app/api/v1/endpoint_modules/root.py @@ -64,11 +64,11 @@ async def api_root(request: Request): "/api/v1/ogc/", "/api/v1/ogc/conformance", "/api/v1/ogc/collections", - "/api/v1/ogc/collections/btaa-records", - "/api/v1/ogc/collections/btaa-records/queryables", - "/api/v1/ogc/collections/btaa-records/sortables", - "/api/v1/ogc/collections/btaa-records/items", - "/api/v1/ogc/collections/btaa-records/items/{recordId}", + "/api/v1/ogc/collections/ogm-records", + "/api/v1/ogc/collections/ogm-records/queryables", + "/api/v1/ogc/collections/ogm-records/sortables", + "/api/v1/ogc/collections/ogm-records/items", + "/api/v1/ogc/collections/ogm-records/items/{recordId}", ], }, } diff --git a/backend/app/api/v1/endpoint_modules/search.py b/backend/app/api/v1/endpoint_modules/search.py index 8658169..5ca57c1 100644 --- a/backend/app/api/v1/endpoint_modules/search.py +++ b/backend/app/api/v1/endpoint_modules/search.py @@ -204,7 +204,7 @@ def _build_semantic_search_cache_key( return CacheService.generate_cache_key( SEARCH_RESULT_CACHE_NAMESPACE, version=SEARCH_RESULT_CACHE_VERSION, - index=os.getenv("ELASTICSEARCH_INDEX", "btaa_geospatial_api"), + index=os.getenv("ELASTICSEARCH_INDEX", "opengeometadata_api"), q=q or "", page=page, per_page=per_page, diff --git a/backend/app/api/v1/endpoint_modules/shapefiles.py b/backend/app/api/v1/endpoint_modules/shapefiles.py index 1cc5ec1..a8439b9 100644 --- a/backend/app/api/v1/endpoint_modules/shapefiles.py +++ b/backend/app/api/v1/endpoint_modules/shapefiles.py @@ -22,7 +22,7 @@ logger = logging.getLogger(__name__) # DuckDB configuration -DUCKDB_DATABASE_PATH = os.getenv("DUCKDB_DATABASE_PATH", "data/duckdb/btaa_ogm_api.duckdb") +DUCKDB_DATABASE_PATH = os.getenv("DUCKDB_DATABASE_PATH", "data/duckdb/opengeometadata_api.duckdb") # Ensure the DuckDB directory exists os.makedirs(os.path.dirname(DUCKDB_DATABASE_PATH), exist_ok=True) diff --git a/backend/app/api/v1/endpoint_modules/slack.py b/backend/app/api/v1/endpoint_modules/slack.py index bef9845..a73a6b6 100644 --- a/backend/app/api/v1/endpoint_modules/slack.py +++ b/backend/app/api/v1/endpoint_modules/slack.py @@ -15,7 +15,7 @@ async def slack_info(): return JSONResponse( content={ - "name": "BTAA Geoportal Slackbot", + "name": "OpenGeoMetadata API Slackbot", "command_endpoint": "/api/v1/slack/commands", "configured": bool(os.getenv("SLACK_SIGNING_SECRET")), } diff --git a/backend/app/api/v1/gazetteer.py b/backend/app/api/v1/gazetteer.py index c46280b..7c9d132 100644 --- a/backend/app/api/v1/gazetteer.py +++ b/backend/app/api/v1/gazetteer.py @@ -10,8 +10,8 @@ from app.services.cache_service import cached_endpoint from db.database import database from db.models import ( - gazetteer_btaa, gazetteer_geonames, + gazetteer_ogm, gazetteer_wof_ancestors, gazetteer_wof_concordances, gazetteer_wof_geojson, @@ -43,7 +43,7 @@ async def list_gazetteers(): select(func.count()).select_from(gazetteer_wof_spr) ) - btaa_count = await database.fetch_val(select(func.count()).select_from(gazetteer_btaa)) + ogm_count = await database.fetch_val(select(func.count()).select_from(gazetteer_ogm)) # Additional WOF table counts wof_ancestors_count = await database.fetch_val( @@ -91,19 +91,19 @@ async def list_gazetteers(): }, }, { - "id": "btaa", + "id": "ogm", "type": "gazetteer", "attributes": { - "name": "BTAA", - "description": "Big Ten Academic Alliance Geoportal gazetteer", - "record_count": btaa_count or 0, - "website": "https://geo.btaa.org/", + "name": "OGM", + "description": "OpenGeoMetadata API gazetteer", + "record_count": ogm_count or 0, + "website": "https://opengeometadata.org/", }, }, ], "meta": { "total_gazetteers": 3, - "total_records": (geonames_count or 0) + (wof_spr_count or 0) + (btaa_count or 0), + "total_records": (geonames_count or 0) + (wof_spr_count or 0) + (ogm_count or 0), }, } except Exception as e: @@ -613,9 +613,9 @@ async def get_wof_details(wok_id: int): raise HTTPException(status_code=500, detail=f"Error getting WOF details: {str(e)}") from e -@router.get("/gazetteers/btaa") +@router.get("/gazetteers/ogm") @cached_endpoint(ttl=GAZETTEER_CACHE_TTL) -async def search_btaa( +async def search_ogm( q: Optional[str] = None, fast_area: Optional[str] = None, state_abbv: Optional[str] = None, @@ -624,7 +624,7 @@ async def search_btaa( limit: int = 20, ): """ - Search BTAA gazetteer. + Search OGM gazetteer. Parameters: - q: General search query (searches fast_area, state_name, and namelsad) @@ -636,7 +636,7 @@ async def search_btaa( """ try: # Build query - query = select(gazetteer_btaa) + query = select(gazetteer_ogm) # Apply filters conditions = [] @@ -646,20 +646,20 @@ async def search_btaa( search_term = f"%{q}%" conditions.append( or_( - gazetteer_btaa.c.fast_area.ilike(search_term), - gazetteer_btaa.c.state_name.ilike(search_term), - gazetteer_btaa.c.namelsad.ilike(search_term), + gazetteer_ogm.c.fast_area.ilike(search_term), + gazetteer_ogm.c.state_name.ilike(search_term), + gazetteer_ogm.c.namelsad.ilike(search_term), ) ) if fast_area: - conditions.append(gazetteer_btaa.c.fast_area == fast_area) + conditions.append(gazetteer_ogm.c.fast_area == fast_area) if state_abbv: - conditions.append(gazetteer_btaa.c.state_abbv == state_abbv.upper()) + conditions.append(gazetteer_ogm.c.state_abbv == state_abbv.upper()) if county_fips: - conditions.append(gazetteer_btaa.c.county_fips == county_fips) + conditions.append(gazetteer_ogm.c.county_fips == county_fips) # Apply conditions to query if conditions: @@ -667,7 +667,7 @@ async def search_btaa( # Apply pagination and ordering query = ( - query.order_by(gazetteer_btaa.c.state_abbv, gazetteer_btaa.c.fast_area) + query.order_by(gazetteer_ogm.c.state_abbv, gazetteer_ogm.c.fast_area) .offset(offset) .limit(limit) ) @@ -676,7 +676,7 @@ async def search_btaa( results = await database.fetch_all(query) # Get total count for pagination - count_query = select(func.count()).select_from(gazetteer_btaa) + count_query = select(func.count()).select_from(gazetteer_ogm) if conditions: count_query = count_query.where(and_(*conditions)) @@ -689,7 +689,7 @@ async def search_btaa( formatted_results.append( { "id": str(record["id"]), - "type": "btaa", + "type": "ogm", "attributes": { "fast_area": record["fast_area"], "bounding_box": record["bounding_box"], @@ -720,8 +720,8 @@ async def search_btaa( } except Exception as e: - logger.error(f"Error searching BTAA: {e}", exc_info=True) - raise HTTPException(status_code=500, detail=f"Error searching BTAA: {str(e)}") from e + logger.error(f"Error searching OGM: {e}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Error searching OGM: {str(e)}") from e @router.get("/gazetteers/search") @@ -729,10 +729,10 @@ async def search_btaa( async def search_all_gazetteers( q: str = Query(..., description="Search query"), gazetteer: Optional[str] = Query( - None, description="Specific gazetteer to search (geonames, wof, btaa, or all)" + None, description="Specific gazetteer to search (geonames, wof, ogm, or all)" ), country_code: Optional[str] = Query(None, description="Two-letter country code"), - state_abbv: Optional[str] = Query(None, description="Two-letter state abbreviation (for BTAA)"), + state_abbv: Optional[str] = Query(None, description="Two-letter state abbreviation (for OGM)"), offset: int = Query(0, description="Result offset for pagination"), limit: int = Query(20, description="Maximum number of results to return"), ): @@ -741,9 +741,9 @@ async def search_all_gazetteers( Parameters: - q: Search query (required) - - gazetteer: Specific gazetteer to search (geonames, wof, btaa, or all) + - gazetteer: Specific gazetteer to search (geonames, wof, ogm, or all) - country_code: Two-letter country code (for GeoNames and WOF) - - state_abbv: Two-letter state abbreviation (for BTAA) + - state_abbv: Two-letter state abbreviation (for OGM) - offset: Result offset for pagination - limit: Maximum number of results to return """ @@ -754,7 +754,7 @@ async def search_all_gazetteers( # Determine which gazetteers to search gazetteers_to_search = [] if not gazetteer or gazetteer.lower() == "all": - gazetteers_to_search = ["geonames", "wof", "btaa"] + gazetteers_to_search = ["geonames", "wof", "ogm"] else: gazetteers_to_search = [gazetteer.lower()] @@ -782,16 +782,16 @@ async def search_all_gazetteers( results.extend(wof_results["data"]) total_count += wof_results["meta"]["total_count"] - # Search BTAA - if "btaa" in gazetteers_to_search: - btaa_results = await search_btaa(q=q, state_abbv=state_abbv, offset=offset, limit=limit) + # Search OGM + if "ogm" in gazetteers_to_search: + ogm_results = await search_ogm(q=q, state_abbv=state_abbv, offset=offset, limit=limit) # Add source to each result - for result in btaa_results["data"]: - result["source"] = "btaa" + for result in ogm_results["data"]: + result["source"] = "ogm" - results.extend(btaa_results["data"]) - total_count += btaa_results["meta"]["total_count"] + results.extend(ogm_results["data"]) + total_count += ogm_results["meta"]["total_count"] return { "data": results[:limit], # Limit results diff --git a/backend/app/api/v1/presenters/resource.py b/backend/app/api/v1/presenters/resource.py index 62cc4c5..96c9715 100644 --- a/backend/app/api/v1/presenters/resource.py +++ b/backend/app/api/v1/presenters/resource.py @@ -145,7 +145,7 @@ def serialize_jsonapi_resource(resource_data, request_url=None): "attributes": nested_attributes if nested_attributes else {}, "meta": { "@context": "https://gin.btaa.org/ld/contexts/ogm-aardvark-btaa.context.jsonld", - "@type": "BtaaAardvarkRecord", + "@type": "OgmAardvarkRecord", "ui": restructured_ui, }, } diff --git a/backend/app/api/v1/shapefiles.py b/backend/app/api/v1/shapefiles.py index cf28167..ea79841 100644 --- a/backend/app/api/v1/shapefiles.py +++ b/backend/app/api/v1/shapefiles.py @@ -27,7 +27,7 @@ logger = logging.getLogger(__name__) # DuckDB configuration -DUCKDB_DATABASE_PATH = os.getenv("DUCKDB_DATABASE_PATH", "data/duckdb/btaa_ogm_api.duckdb") +DUCKDB_DATABASE_PATH = os.getenv("DUCKDB_DATABASE_PATH", "data/duckdb/opengeometadata_api.duckdb") # Ensure the DuckDB directory exists os.makedirs(os.path.dirname(DUCKDB_DATABASE_PATH), exist_ok=True) diff --git a/backend/app/api/v1/utils.py b/backend/app/api/v1/utils.py index 8eabf0b..6bd10cd 100644 --- a/backend/app/api/v1/utils.py +++ b/backend/app/api/v1/utils.py @@ -571,7 +571,7 @@ def create_gazetteer_meta_and_links( limit: Number of results per page offset: Number of results to skip total_count: Total number of results - gazetteer_name: Name of the gazetteer (geonames, wof, btaa) + gazetteer_name: Name of the gazetteer (geonames, wof, ogm) allowed_params: List of allowed parameter names for strong parameters Returns: diff --git a/backend/app/elasticsearch/client.py b/backend/app/elasticsearch/client.py index 89e58f1..bc44e18 100644 --- a/backend/app/elasticsearch/client.py +++ b/backend/app/elasticsearch/client.py @@ -31,7 +31,7 @@ async def init_elasticsearch(): """Initialize Elasticsearch index and mappings.""" from .mappings import INDEX_MAPPING - index_name = os.getenv("ELASTICSEARCH_INDEX", "btaa_geospatial_api") + index_name = os.getenv("ELASTICSEARCH_INDEX", "opengeometadata_api") try: # Test the connection diff --git a/backend/app/elasticsearch/index.py b/backend/app/elasticsearch/index.py index a28e818..8562539 100644 --- a/backend/app/elasticsearch/index.py +++ b/backend/app/elasticsearch/index.py @@ -222,7 +222,7 @@ def _coerce_boolean(value): async def index_resources(): """Index all resources from PostgreSQL into Elasticsearch.""" - index_name = os.getenv("ELASTICSEARCH_INDEX", "btaa_geospatial_api") + index_name = os.getenv("ELASTICSEARCH_INDEX", "opengeometadata_api") if await es.indices.exists(index=index_name): await es.indices.delete(index=index_name) @@ -980,7 +980,7 @@ async def perform_individual_indexing(resources_data, index_name, batch_size=100 async def reindex_resources(): """Reindex all resources from PostgreSQL into Elasticsearch with the new mapping.""" - index_name = os.getenv("ELASTICSEARCH_INDEX", "btaa_geospatial_api") + index_name = os.getenv("ELASTICSEARCH_INDEX", "opengeometadata_api") try: # Delete the existing index if it exists diff --git a/backend/app/elasticsearch/mappings.py b/backend/app/elasticsearch/mappings.py index c968a64..aab3de2 100644 --- a/backend/app/elasticsearch/mappings.py +++ b/backend/app/elasticsearch/mappings.py @@ -158,7 +158,7 @@ "gbl_mdmodified_dt": {"type": "date", "ignore_malformed": True}, # Legacy references blob retained for compatibility (disabled indexing) "dct_references_s": {"type": "object", "enabled": False}, - # BTAA-specific OGM Aardvark fields + # OGM-specific OGM Aardvark fields "b1g_code_s": {"type": "keyword"}, "b1g_status_s": {"type": "keyword"}, "b1g_dct_accrualMethod_s": {"type": "keyword"}, diff --git a/backend/app/elasticsearch/search.py b/backend/app/elasticsearch/search.py index e206af8..f5a74c6 100644 --- a/backend/app/elasticsearch/search.py +++ b/backend/app/elasticsearch/search.py @@ -115,7 +115,7 @@ def _build_case_insensitive_facet_regex(query_text: str) -> str: } DIRECT_FILTER_FIELDS = { - # BTAA code is mapped as a keyword already, so filters should target the field directly. + # OGM code is mapped as a keyword already, so filters should target the field directly. "b1g_code_s", "b1g_language_sm", } @@ -1147,7 +1147,7 @@ def from_inputs( facets=facets, adv_q=adv_q, hydrate_hits=hydrate_hits, - index_name=os.getenv("ELASTICSEARCH_INDEX", "btaa_geospatial_api"), + index_name=os.getenv("ELASTICSEARCH_INDEX", "opengeometadata_api"), ) @property @@ -2242,7 +2242,7 @@ async def map_h3_aggregation( bbox: 'west,south,east,north'. resolution: 2–8. Returns {"resolution": int, "hexes": [[h3_str, count], ...], "globalCount": int}. """ - index_name = os.getenv("ELASTICSEARCH_INDEX", "btaa_geospatial_api") + index_name = os.getenv("ELASTICSEARCH_INDEX", "opengeometadata_api") if resolution < 2 or resolution > 8: resolution = 5 filter_clauses = [] @@ -2704,7 +2704,7 @@ async def get_facet_values( ValueError: If facet_name is invalid HTTPException: If Elasticsearch query fails """ - index_name = os.getenv("ELASTICSEARCH_INDEX", "btaa_geospatial_api") + index_name = os.getenv("ELASTICSEARCH_INDEX", "opengeometadata_api") overall_start = time.perf_counter() # Get facet aggregation configuration @@ -3041,7 +3041,7 @@ async def find_similar_resources(resource_id: str, limit: int = 12) -> list: Returns: List of resource IDs ordered by similarity score """ - index_name = os.getenv("ELASTICSEARCH_INDEX", "btaa_geospatial_api") + index_name = os.getenv("ELASTICSEARCH_INDEX", "opengeometadata_api") try: # Use HEAD/exists instead of GET so retired database records that are diff --git a/backend/app/gazetteer/download.py b/backend/app/gazetteer/download.py index efdf8f9..cf87cf2 100755 --- a/backend/app/gazetteer/download.py +++ b/backend/app/gazetteer/download.py @@ -11,7 +11,7 @@ python app/gazetteer/download.py [options] Arguments: - --gazetteer Gazetteer to download (wof, btaa, geonames). Can be specified multiple times. + --gazetteer Gazetteer to download (wof, ogm, geonames). Can be specified multiple times. --download Download and extract data. --export Export data to CSV (for gazetteers that need this step). --all Run all operations for the specified gazetteer(s). diff --git a/backend/app/gazetteer/import_all.py b/backend/app/gazetteer/import_all.py index e82e711..06fcf80 100644 --- a/backend/app/gazetteer/import_all.py +++ b/backend/app/gazetteer/import_all.py @@ -5,7 +5,7 @@ This script runs all the gazetteer importers in sequence. - GeoNames: Imports data from tab-delimited .txt files - WOF: Imports data from .csv files -- BTAA: Imports data from .csv files +- OGM: Imports data from .csv files - FAST: Imports data from MARCXML files """ @@ -21,9 +21,9 @@ # Add parent directory to path to import modules sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) -from app.gazetteer.importers.btaa_importer import BtaaImporter from app.gazetteer.importers.fast_importer import FastImporter from app.gazetteer.importers.geonames_importer import GeonamesImporter +from app.gazetteer.importers.ogm_importer import OgmImporter from app.gazetteer.importers.wof_importer import WofImporter # Configure logging @@ -55,7 +55,7 @@ async def import_all( Run all gazetteer importers. Args: - gazetteer_types: List of gazetteer types to import ('geonames', 'wof', 'btaa', 'fast'). + gazetteer_types: List of gazetteer types to import ('geonames', 'wof', 'ogm', 'fast'). If None, all gazetteers will be imported. data_dir: Base directory for gazetteer data. If None, default directories will be used. @@ -67,7 +67,7 @@ async def import_all( # Use all gazetteer types if none specified if not gazetteer_types: - gazetteer_types = ["geonames", "wof", "btaa", "fast"] + gazetteer_types = ["geonames", "wof", "ogm", "fast"] results = {} @@ -90,9 +90,9 @@ async def import_all( logger.info(" - concordances.csv: Concordances to other systems") logger.info(" - geojson.csv: GeoJSON data (if available)") logger.info(" - names.csv: Alternative names") - elif gazetteer_type == "btaa": - importer_dir = os.path.join(data_dir, "btaa") if data_dir else None - importer = BtaaImporter(data_directory=importer_dir) + elif gazetteer_type == "ogm": + importer_dir = os.path.join(data_dir, "ogm") if data_dir else None + importer = OgmImporter(data_directory=importer_dir) elif gazetteer_type == "fast": importer_dir = os.path.join(data_dir, "fast") if data_dir else None importer = FastImporter(data_directory=importer_dir) @@ -150,7 +150,7 @@ def parse_args(): parser.add_argument( "--gazetteers", nargs="+", - choices=["geonames", "wof", "btaa", "fast", "all"], + choices=["geonames", "wof", "ogm", "fast", "all"], default=["all"], help="Gazetteers to import (default: all)", ) @@ -168,7 +168,7 @@ def parse_args(): # Convert 'all' to all gazetteer types gazetteer_types = [] if "all" in args.gazetteers: - gazetteer_types = ["geonames", "wof", "btaa", "fast"] + gazetteer_types = ["geonames", "wof", "ogm", "fast"] else: gazetteer_types = args.gazetteers diff --git a/backend/app/gazetteer/importers/__init__.py b/backend/app/gazetteer/importers/__init__.py index 5766bee..524d38d 100644 --- a/backend/app/gazetteer/importers/__init__.py +++ b/backend/app/gazetteer/importers/__init__.py @@ -1,14 +1,14 @@ # Gazetteer importers package from .base_importer import BaseImporter -from .btaa_importer import BtaaImporter from .fast_importer import FastImporter from .geonames_importer import GeonamesImporter +from .ogm_importer import OgmImporter from .wof_importer import WofImporter __all__ = [ "BaseImporter", - "BtaaImporter", + "OgmImporter", "FastImporter", "GeonamesImporter", "WofImporter", diff --git a/backend/app/gazetteer/importers/btaa_importer.py b/backend/app/gazetteer/importers/ogm_importer.py similarity index 92% rename from backend/app/gazetteer/importers/btaa_importer.py rename to backend/app/gazetteer/importers/ogm_importer.py index 64bcfc1..7bb49de 100644 --- a/backend/app/gazetteer/importers/btaa_importer.py +++ b/backend/app/gazetteer/importers/ogm_importer.py @@ -4,22 +4,22 @@ from datetime import datetime from typing import Any, Dict -from db.models import gazetteer_btaa +from db.models import gazetteer_ogm from .base_importer import BaseImporter logger = logging.getLogger(__name__) -class BtaaImporter(BaseImporter): - """Importer for BTAA gazetteer data.""" +class OgmImporter(BaseImporter): + """Importer for OGM gazetteer data.""" - # BTAA-specific data directory + # OGM-specific data directory DATA_DIR = os.path.join( os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))), "data", "gazetteers", - "btaa", + "ogm", ) # Map CSV column names to database field names @@ -36,19 +36,19 @@ class BtaaImporter(BaseImporter): } # Smaller chunk size to avoid PostgreSQL parameter limits (similar to GeoNames) - # The BTAA table has 9 fields + 2 for created_at/updated_at, so 11 params per record + # The OGM table has 9 fields + 2 for created_at/updated_at, so 11 params per record # 32767 / 11 ≈ 2979, using 2000 to be safe CHUNK_SIZE = 2000 def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.data_directory = kwargs.get("data_directory") or self.DATA_DIR - self.table = gazetteer_btaa - self.table_name = "gazetteer_btaa" + self.table = gazetteer_ogm + self.table_name = gazetteer_ogm.name def clean_record(self, record: Dict[str, Any]) -> Dict[str, Any]: """ - Clean and transform a BTAA record before insertion. + Clean and transform an OGM record before insertion. Args: record: The raw record from the CSV. @@ -73,7 +73,7 @@ def clean_record(self, record: Dict[str, Any]) -> Dict[str, Any]: async def import_data(self) -> Dict[str, Any]: """ - Import BTAA data from CSV files to the database. + Import OGM data from CSV files to the database. Returns: Dictionary with import statistics. @@ -171,7 +171,7 @@ async def import_data(self) -> Dict[str, Any]: logging.basicConfig(level=logging.INFO) async def run_import(): - importer = BtaaImporter() + importer = OgmImporter() result = await importer.import_data() print(result) diff --git a/backend/app/main.py b/backend/app/main.py index d904118..6654ac4 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -225,10 +225,10 @@ async def dispatch(self, request: Request, call_next): "X-Requested-With", "X-CSRF-Token", "X-Visit-Token", - "X-BTAA-Client-Name", - "X-BTAA-Client-Version", - "X-BTAA-Client-Channel", - "X-BTAA-Client-Instance", + "X-OGM-Client-Name", + "X-OGM-Client-Version", + "X-OGM-Client-Channel", + "X-OGM-Client-Instance", "X-Turnstile-Session", ], expose_headers=[ diff --git a/backend/app/middleware/rate_limit_middleware.py b/backend/app/middleware/rate_limit_middleware.py index c7768da..641ff70 100644 --- a/backend/app/middleware/rate_limit_middleware.py +++ b/backend/app/middleware/rate_limit_middleware.py @@ -40,8 +40,8 @@ def _bypass_rate_limit_for_tests() -> bool: } DOCUMENTATION_ASSET_PATHS = { "/static/brand.css", - "/static/btaa-gin-white.png", - "/static/btaa-logo-white.png", + "/static/opengeometadata-bauhaus-logo.svg", + "/static/opengeometadata-map-legend-logo-composite.svg", "/static/favicon.ico", } @@ -185,7 +185,7 @@ async def dispatch(self, request: Request, call_next): tier_id = tier_info.get("tier_id") if tier_id is None: - if tier_info.get("source") == "env:BTAA_GEOSPATIAL_API_KEY": + if tier_info.get("source") == "env:OPENGEOMETADATA_API_KEY": logger.debug( "Skipping API usage logging for configured server API key on %s", request.url.path, diff --git a/backend/app/middleware/turnstile_middleware.py b/backend/app/middleware/turnstile_middleware.py index b47230d..acb21bd 100644 --- a/backend/app/middleware/turnstile_middleware.py +++ b/backend/app/middleware/turnstile_middleware.py @@ -33,9 +33,9 @@ def _path_matches(path: str, protected_path: str) -> bool: def _is_frontend_gate_request(request: Request) -> bool: - if request.headers.get("X-BTAA-Turnstile-Gate"): + if request.headers.get("X-OGM-Turnstile-Gate"): return True - if request.headers.get("X-BTAA-Client-Channel", "").lower() == "browser": + if request.headers.get("X-OGM-Client-Channel", "").lower() == "browser": return True return bool(request.headers.get("X-Visit-Token")) diff --git a/backend/app/services/api_key_service.py b/backend/app/services/api_key_service.py index 13add17..9e3522b 100644 --- a/backend/app/services/api_key_service.py +++ b/backend/app/services/api_key_service.py @@ -14,7 +14,10 @@ logger = logging.getLogger(__name__) API_KEY_HASH_ITERATIONS = 600_000 -DEFAULT_API_KEY_HASH_SECRET = "btaa-api-key-hash-v2" +DEFAULT_API_KEY_HASH_SECRET = "ogm-api-key-hash-v3" +# Keep this exact historical salt until every stored fallback hash has been +# upgraded during successful authentication. +LEGACY_DEFAULT_API_KEY_HASH_SECRET = "btaa-api-key-hash-v2" API_KEY_TIER_CACHE_TTL_SECONDS = float(os.getenv("API_KEY_TIER_CACHE_TTL_SECONDS", "60")) API_KEY_LAST_USED_UPDATE_INTERVAL_SECONDS = float( os.getenv("API_KEY_LAST_USED_UPDATE_INTERVAL_SECONDS", "60") @@ -49,13 +52,13 @@ def _cache_lookup_key(key: str) -> str: def _configured_server_api_keys() -> List[str]: """Return server-injected API keys configured through the environment. - `BTAA_GEOSPATIAL_API_KEY` is injected into React Router SSR and nginx BFF + `OPENGEOMETADATA_API_KEY` is injected into React Router SSR and nginx BFF requests. It must remain unlimited even if a destination-local API key table is stale after a DB sync or secret rotation. """ raw_values = [ - os.getenv("BTAA_GEOSPATIAL_API_KEY", ""), - os.getenv("BTAA_GEOSPATIAL_API_KEYS", ""), + os.getenv("OPENGEOMETADATA_API_KEY", ""), + os.getenv("OPENGEOMETADATA_API_KEYS", ""), ] keys: List[str] = [] @@ -73,13 +76,13 @@ def _configured_server_key_tier(key: str) -> Optional[Dict[str, Any]]: if hmac.compare_digest(key, configured_key): return { "tier_id": None, - "tier_name": "btaa_primary", - "display_name": "BTAA Geoportal Frontend", + "tier_name": "ogm_primary", + "display_name": "OpenGeoMetadata API Frontend", "requests_per_minute": None, "api_key_id": None, "key_hash": APIKeyService.legacy_hash_api_key(key), "allowed_ips": None, - "source": "env:BTAA_GEOSPATIAL_API_KEY", + "source": "env:OPENGEOMETADATA_API_KEY", } return None @@ -171,19 +174,29 @@ def generate_api_key() -> str: @staticmethod def hash_api_key(key: str) -> str: """Hash an API key using PBKDF2-HMAC-SHA256.""" - salt = ( + secret = ( os.getenv("API_KEY_HASH_SECRET") or os.getenv("SECRET_KEY") or DEFAULT_API_KEY_HASH_SECRET - ).encode("utf-8") + ) + return APIKeyService._pbkdf2_hash_api_key(key, secret) + + @staticmethod + def _pbkdf2_hash_api_key(key: str, secret: str) -> str: + """Hash an API key with an explicit PBKDF2 salt.""" return hashlib.pbkdf2_hmac( "sha256", key.encode("utf-8"), - salt, + secret.encode("utf-8"), API_KEY_HASH_ITERATIONS, dklen=32, ).hex() + @staticmethod + def legacy_default_hash_api_key(key: str) -> str: + """Hash with the pre-OGM fallback salt for an in-place compatibility upgrade.""" + return APIKeyService._pbkdf2_hash_api_key(key, LEGACY_DEFAULT_API_KEY_HASH_SECRET) + @staticmethod def legacy_hash_api_key(key: str) -> str: """Legacy SHA-256 hash for backward compatibility with stored keys.""" @@ -212,10 +225,9 @@ async def validate_api_key( return cached_tier key_hash = self.hash_api_key(key) + legacy_default_key_hash = self.legacy_default_hash_api_key(key) legacy_key_hash = self.legacy_hash_api_key(key) - candidate_hashes = [key_hash] - if legacy_key_hash != key_hash: - candidate_hashes.append(legacy_key_hash) + candidate_hashes = list(dict.fromkeys([key_hash, legacy_default_key_hash, legacy_key_hash])) async with async_session() as session: try: @@ -251,7 +263,7 @@ async def validate_api_key( } update_values = {"last_used_at": datetime.utcnow()} - if stored_key_hash == legacy_key_hash and stored_key_hash != key_hash: + if stored_key_hash != key_hash: update_values["key_hash"] = key_hash elif not self._last_used_update_due(api_key_id): update_values = {} diff --git a/backend/app/services/api_usage_log_service.py b/backend/app/services/api_usage_log_service.py index a154acd..2505353 100644 --- a/backend/app/services/api_usage_log_service.py +++ b/backend/app/services/api_usage_log_service.py @@ -20,10 +20,10 @@ def _extract_client_properties(self, request) -> Dict[str, str]: "client_instance": 100, } client_properties = { - "client_name": request.headers.get("X-BTAA-Client-Name"), - "client_version": request.headers.get("X-BTAA-Client-Version"), - "client_channel": request.headers.get("X-BTAA-Client-Channel"), - "client_instance": request.headers.get("X-BTAA-Client-Instance"), + "client_name": request.headers.get("X-OGM-Client-Name"), + "client_version": request.headers.get("X-OGM-Client-Version"), + "client_channel": request.headers.get("X-OGM-Client-Channel"), + "client_instance": request.headers.get("X-OGM-Client-Instance"), } return { key: value[: max_lengths[key]] diff --git a/backend/app/services/bridge_sync/report.py b/backend/app/services/bridge_sync/report.py index a132618..f472529 100644 --- a/backend/app/services/bridge_sync/report.py +++ b/backend/app/services/bridge_sync/report.py @@ -358,7 +358,7 @@ def build_bridge_sync_report_html( };"> -
BTAA Geoportal
+
OpenGeoMetadata API

Nightly Bridge Sync Report

{ @@ -480,7 +480,7 @@ def build_bridge_sync_report_html( }

- Sent automatically after the bridge sync task finalized. Colors and visual rhythm follow the BTAA Geoportal interface: deep BTAA blue, active blue, white panels, and quiet slate metadata. + Sent automatically after the bridge sync task finalized. Colors and visual rhythm follow the OpenGeoMetadata API interface: deep OGM blue, active blue, white panels, and quiet slate metadata.

@@ -500,7 +500,7 @@ def build_bridge_sync_report_text( stats = _stats_for_run(run) alerts = _alert_items(run, recent_runs or []) lines = [ - "BTAA Geoportal Nightly Bridge Sync Report", + "OpenGeoMetadata API Nightly Bridge Sync Report", f"Run: #{run.get('bridge_id')}", f"Status: {run.get('bridge_status') or 'unknown'}", f"Trigger: {run.get('bridge_trigger') or 'unknown'}", @@ -530,7 +530,7 @@ def _build_message( ) -> EmailMessage: stats = _stats_for_run(run) status = str(run.get("bridge_status") or "unknown").upper() - subject_prefix = os.getenv("BRIDGE_SYNC_REPORT_SUBJECT_PREFIX", "BTAA Geoportal") + subject_prefix = os.getenv("BRIDGE_SYNC_REPORT_SUBJECT_PREFIX", "OpenGeoMetadata API") environment = os.getenv("KAMAL_DEST") or os.getenv("APP_ENV") or os.getenv("RAILS_ENV") subject_env = f" [{environment}]" if environment else "" subject = ( @@ -540,15 +540,15 @@ def _build_message( sender = os.getenv("BRIDGE_SYNC_REPORT_FROM") or os.getenv("SMTP_FROM") if not sender: - sender = "BTAA Geoportal " + sender = "OpenGeoMetadata API " if "<" not in sender and ">" not in sender: - sender = formataddr(("BTAA Geoportal", sender)) + sender = formataddr(("OpenGeoMetadata API", sender)) message = EmailMessage() message["Subject"] = subject message["From"] = sender message["To"] = ", ".join(recipients) - message["Message-ID"] = make_msgid(domain="geo.btaa.org") + message["Message-ID"] = make_msgid(domain="ogm.geo4lib.app") message.set_content(build_bridge_sync_report_text(run, recent_runs=recent_runs)) message.add_alternative( build_bridge_sync_report_html(run, recent_runs=recent_runs, environment=environment), diff --git a/backend/app/services/bridge_sync/search_index.py b/backend/app/services/bridge_sync/search_index.py index 2dffe01..82d3fe7 100644 --- a/backend/app/services/bridge_sync/search_index.py +++ b/backend/app/services/bridge_sync/search_index.py @@ -117,7 +117,7 @@ async def index_changed_resources(resource_ids: Iterable[str]) -> dict[str, Any] if not database.is_connected: await database.connect() - index_name = os.getenv("ELASTICSEARCH_INDEX", "btaa_geospatial_api") + index_name = os.getenv("ELASTICSEARCH_INDEX", "opengeometadata_api") batch_size = _refresh_batch_size() batches = 0 indexed = 0 diff --git a/backend/app/services/citation_formats_service.py b/backend/app/services/citation_formats_service.py index 28aaa56..c9f78b5 100644 --- a/backend/app/services/citation_formats_service.py +++ b/backend/app/services/citation_formats_service.py @@ -62,7 +62,7 @@ def __init__( self, document: Dict[str, Any], distribution_context: Optional[DistributionContext] = None, - base_url: str = "https://geoportal.btaa.org", + base_url: str = "https://ogm.geo4lib.app", ): self.document = document self.distribution_context = distribution_context or DistributionContext( @@ -194,7 +194,7 @@ def to_json_ld(self, resource_id: str) -> Dict[str, Any]: # Included in DataCatalog (Geoportal) obj["includedInDataCatalog"] = { "@type": "DataCatalog", - "name": "Big Ten Academic Alliance Geoportal", + "name": "OpenGeoMetadata API", "url": self.base_url, } # Distribution (download/view links) diff --git a/backend/app/services/citation_service.py b/backend/app/services/citation_service.py index 8ecb024..3356617 100644 --- a/backend/app/services/citation_service.py +++ b/backend/app/services/citation_service.py @@ -203,12 +203,12 @@ def _format_mla(self) -> str: if len(creators) > 1: author = ", ".join(creators[:-1]) + ", and " + creators[-1] title = self.document.get("dct_title_s") or "Untitled" - publisher = self._get_apa_publisher() or "Big Ten Academic Alliance Geoportal" + publisher = self._get_apa_publisher() or "OpenGeoMetadata API" date = self.document.get("dct_issued_s") or "n.d." url = self._get_url() or "" out = f'{author}. "{title}." ' - out += f"Big Ten Academic Alliance Geoportal, {publisher}, {date}" + out += f"OpenGeoMetadata API, {publisher}, {date}" if url: out += f", {url}" out += "." @@ -222,7 +222,7 @@ def _format_chicago(self) -> str: author = ", ".join(creators[:-1]) + ", and " + creators[-1] year = self._extract_year() title = self.document.get("dct_title_s") or "Untitled" - publisher = self._get_apa_publisher() or "Big Ten Academic Alliance Geoportal" + publisher = self._get_apa_publisher() or "OpenGeoMetadata API" url = self._get_url() or "" out = f'{author}. {year}. "{title}." {publisher}.' diff --git a/backend/app/services/distribution_repository.py b/backend/app/services/distribution_repository.py index 1dd0b01..1f57797 100644 --- a/backend/app/services/distribution_repository.py +++ b/backend/app/services/distribution_repository.py @@ -45,7 +45,7 @@ async def fetch_resource_distributions( Fetch all distribution rows for a single resource. Args: - resource_id: The BTAA resource identifier. + resource_id: The OGM resource identifier. session: Optional AsyncSession to reuse an existing transaction context. Returns: diff --git a/backend/app/services/feedback_service.py b/backend/app/services/feedback_service.py index c8ed5f1..35c6a80 100644 --- a/backend/app/services/feedback_service.py +++ b/backend/app/services/feedback_service.py @@ -15,7 +15,7 @@ "Harmful language", "Other", } -DEFAULT_FEEDBACK_RECIPIENTS = "majew030@umn.edu,btaa-gdp@umn.edu,geoportal@btaa.org" +DEFAULT_FEEDBACK_RECIPIENTS = "" class FeedbackDeliveryUnavailable(RuntimeError): @@ -64,11 +64,11 @@ def _sender() -> str: sender = os.getenv("FEEDBACK_FROM") or os.getenv("SMTP_FROM") if sender: return sender - return formataddr(("BTAA Geoportal", "no-reply@geo.btaa.org")) + return formataddr(("OpenGeoMetadata API", "no-reply@ogm.geo4lib.app")) def _subject(topic: str) -> str: - prefix = os.getenv("FEEDBACK_SUBJECT_PREFIX", "BTAA Geoportal Feedback") + prefix = os.getenv("FEEDBACK_SUBJECT_PREFIX", "OpenGeoMetadata API Feedback") return f"{prefix}: {topic}" @@ -90,7 +90,7 @@ def _build_message(submission: FeedbackSubmission, recipients: list[str]) -> Ema message.set_content( "\n".join( [ - "A BTAA Geoportal feedback form was submitted.", + "An OpenGeoMetadata API feedback form was submitted.", "", f"Topic: {submission.topic}", f"Name: {submitted_by}", diff --git a/backend/app/services/image_service.py b/backend/app/services/image_service.py index e6a9e45..f6dd319 100644 --- a/backend/app/services/image_service.py +++ b/backend/app/services/image_service.py @@ -161,7 +161,7 @@ def _get_manifest(self, manifest_url: str) -> Optional[Dict]: try: self.logger.info(f"🐌 Cache MISS for manifest {manifest_url}") # Use User-Agent header to avoid 403 errors from servers that block bots - headers = {"User-Agent": "BTAA-Geospatial-Data-API/1.0 (https://geo.btaa.org/)"} + headers = {"User-Agent": "OpenGeoMetadata-API/1.0 (https://opengeometadata.org/)"} # Increased timeout for slow servers response = requests.get(manifest_url, timeout=5.0, headers=headers) diff --git a/backend/app/services/mcp_service.py b/backend/app/services/mcp_service.py index 515041a..fb5a025 100644 --- a/backend/app/services/mcp_service.py +++ b/backend/app/services/mcp_service.py @@ -721,10 +721,8 @@ async def _get_resource_viewer(self, arguments: Dict[str, Any]) -> CallToolResul def _public_api_base(self) -> str: """Resolve base URL for calling this API over HTTP.""" - base = ( - os.getenv("OPENGEOMETADATA_API_BASE_URL") - or os.getenv("BTAA_GEOSPATIAL_API_BASE_URL") - or os.getenv("APPLICATION_URL", "http://localhost:8000") + base = os.getenv("OPENGEOMETADATA_API_BASE_URL") or os.getenv( + "APPLICATION_URL", "http://localhost:8000" ) base = base.rstrip("/") if base.endswith("/api/v1"): @@ -741,7 +739,7 @@ async def _api_request( url = f"{self._public_api_base()}/api/v1{path}" timeout = aiohttp.ClientTimeout(total=30) headers = {} - api_key = os.getenv("OPENGEOMETADATA_API_KEY") or os.getenv("BTAA_GEOSPATIAL_API_KEY") + api_key = os.getenv("OPENGEOMETADATA_API_KEY") if api_key: headers["X-API-Key"] = api_key async with aiohttp.ClientSession(timeout=timeout) as session: diff --git a/backend/app/services/nominatim_service.py b/backend/app/services/nominatim_service.py index 8f79c27..07240cf 100644 --- a/backend/app/services/nominatim_service.py +++ b/backend/app/services/nominatim_service.py @@ -20,7 +20,7 @@ ) NOMINATIM_USER_AGENT = os.getenv( "NOMINATIM_USER_AGENT", - "BTAA-Geoportal/1.0 (+https://geo.btaa.org)", + "OpenGeoMetadata/1.0 (+https://opengeometadata.org)", ) NOMINATIM_TIMEOUT_SECONDS = float(os.getenv("NOMINATIM_TIMEOUT_SECONDS", "10")) NOMINATIM_HARD_MAX_LIMIT = 5 diff --git a/backend/app/services/ogc_projector.py b/backend/app/services/ogc_projector.py index 22adfcf..61fcbaa 100644 --- a/backend/app/services/ogc_projector.py +++ b/backend/app/services/ogc_projector.py @@ -39,7 +39,7 @@ def map_record_to_properties(attributes: Dict[str, Any]) -> Dict[str, Any]: @staticmethod def build_item( - request_url: str, resource: Dict[str, Any], collection_id: str = "btaa-records" + request_url: str, resource: Dict[str, Any], collection_id: str = "ogm-records" ) -> Dict[str, Any]: """Builds a GeoJSON Feature representing a single record item.""" @@ -115,7 +115,7 @@ def build_conformance() -> Dict[str, Any]: def build_collections(request_url: str) -> Dict[str, Any]: """Builds the collections response.""" base_url = request_url.split("/collections")[0] - collection = OGCResponseProjector.build_collection(request_url, "btaa-records") + collection = OGCResponseProjector.build_collection(request_url, "ogm-records") return { "collections": [collection], @@ -173,7 +173,7 @@ def build_items_response( search_results: Dict[str, Any], page: int, limit: int, - collection_id: str = "btaa-records", + collection_id: str = "ogm-records", ) -> Dict[str, Any]: """Builds an Item Collection (Feature Collection) response from search results.""" features = [] diff --git a/backend/app/services/ogm_field_mapper.py b/backend/app/services/ogm_field_mapper.py index fb3c56d..0ad2a3e 100644 --- a/backend/app/services/ogm_field_mapper.py +++ b/backend/app/services/ogm_field_mapper.py @@ -2,7 +2,7 @@ OGM Field Mapper Service This service handles mapping between database column names (which are downcased) -and proper OGM Aardvark field names for BTAA flavored records. +and proper OGM Aardvark field names for OGM flavored records. """ from typing import Any, Dict @@ -20,7 +20,7 @@ class OGMFieldMapper: """ # Mapping from database column names to proper OGM field names - # Based on the BTAA OGM Aardvark schema + # Based on the OGM Aardvark schema FIELD_MAPPING = { # Standard OGM Aardvark fields "gbl_mdversion_s": "gbl_mdVersion_s", @@ -34,7 +34,7 @@ class OGMFieldMapper: "gbl_suppressed_b": "gbl_suppressed_b", "gbl_georeferenced_b": "gbl_georeferenced_b", "gbl_displaynote_sm": "gbl_displayNote_sm", - # BTAA-specific fields (these may not exist in current DB but are in schema) + # OGM-specific fields (these may not exist in current DB but are in schema) "b1g_code_s": "b1g_code_s", "b1g_status_s": "b1g_status_s", "b1g_dct_accrualmethod_s": "b1g_dct_accrualMethod_s", @@ -90,7 +90,7 @@ def map_resource_fields(cls, resource_dict: Dict[str, Any]) -> Dict[str, Any]: @classmethod def get_required_fields(cls) -> list: """ - Returns the list of required fields according to the BTAA OGM Aardvark schema. + Returns the list of required fields according to the OGM Aardvark schema. Returns: List of required field names @@ -114,7 +114,7 @@ def get_required_fields(cls) -> list: @classmethod def get_all_schema_fields(cls) -> list: """ - Returns the list of all fields defined in the BTAA OGM Aardvark schema. + Returns the list of all fields defined in the OGM Aardvark schema. Returns: List of all schema field names diff --git a/backend/app/services/search_service.py b/backend/app/services/search_service.py index 2ba265c..843244b 100644 --- a/backend/app/services/search_service.py +++ b/backend/app/services/search_service.py @@ -99,7 +99,7 @@ def _search_error_type(error: object) -> str: class SearchService: def __init__(self): - self.index_name = os.getenv("ELASTICSEARCH_INDEX", "btaa_geospatial_api") + self.index_name = os.getenv("ELASTICSEARCH_INDEX", "opengeometadata_api") self.es = es async def search( diff --git a/backend/app/services/sitemap_service.py b/backend/app/services/sitemap_service.py index c0e2f68..718d8bf 100644 --- a/backend/app/services/sitemap_service.py +++ b/backend/app/services/sitemap_service.py @@ -298,7 +298,7 @@ def build_robots_txt(base_url: str | None = None, indexing_enabled: bool | None app_url = _application_url(base_url) lines = [ - "# Production robots rules for the BTAA Geoportal.", + "# Production robots rules for the OpenGeoMetadata API.", "User-agent: *", "Allow: /", "Disallow: /api/", diff --git a/backend/app/services/slackbot_service.py b/backend/app/services/slackbot_service.py index 1f9c64f..f548d0b 100644 --- a/backend/app/services/slackbot_service.py +++ b/backend/app/services/slackbot_service.py @@ -52,10 +52,10 @@ def parse_slack_command(text: str | None) -> SlackCommand: """Parse a compact slash command grammar. Supported examples: - - /btaa - - /btaa help - - /btaa search lakes - - /btaa lakes + - /ogm + - /ogm help + - /ogm search lakes + - /ogm lakes """ cleaned = (text or "").strip() if not cleaned: @@ -84,13 +84,13 @@ async def handle_slack_command(form_data: dict[str, Any]) -> dict[str, Any]: def help_response() -> dict[str, Any]: - command = os.getenv("SLACK_BOT_COMMAND", "/btaa") + command = os.getenv("SLACK_BOT_COMMAND", "/ogm") text = f"Try `{command} search minnesota lakes`, `{command} sanborn maps`, or `{command} help`." return { "response_type": "ephemeral", "text": text, "blocks": [ - {"type": "section", "text": {"type": "mrkdwn", "text": "*BTAA Geoportal*"}}, + {"type": "section", "text": {"type": "mrkdwn", "text": "*OpenGeoMetadata API*"}}, {"type": "section", "text": {"type": "mrkdwn", "text": text}}, ], } @@ -116,13 +116,13 @@ async def search_response(query: str | None) -> dict[str, Any]: if not items: return { "response_type": "ephemeral", - "text": f"No BTAA Geoportal results found for `{query}`.", + "text": f"No OpenGeoMetadata API results found for `{query}`.", "blocks": [ { "type": "section", "text": { "type": "mrkdwn", - "text": f"No BTAA Geoportal results found for `{query}`.", + "text": f"No OpenGeoMetadata API results found for `{query}`.", }, } ], @@ -133,7 +133,7 @@ async def search_response(query: str | None) -> dict[str, Any]: "type": "section", "text": { "type": "mrkdwn", - "text": f"*BTAA Geoportal results for `{query}`* ({total:,} found)", + "text": f"*OpenGeoMetadata API results for `{query}`* ({total:,} found)", }, } ] @@ -154,7 +154,7 @@ async def search_response(query: str | None) -> dict[str, Any]: return { "response_type": "ephemeral", - "text": f"BTAA Geoportal results for {query}", + "text": f"OpenGeoMetadata API results for {query}", "blocks": blocks, } @@ -261,8 +261,8 @@ def _base_url() -> str: base = ( os.getenv("GEOPORTAL_BASE_URL") or os.getenv("APPLICATION_URL") - or os.getenv("BTAA_GEOSPATIAL_API_BASE_URL") - or "https://geoportal.btaa.org" + or os.getenv("OPENGEOMETADATA_API_BASE_URL") + or "https://ogm.geo4lib.app" ) base = base.rstrip("/") if base.endswith("/api/v1"): diff --git a/backend/app/services/turnstile_service.py b/backend/app/services/turnstile_service.py index 5b2f6fe..8ef9815 100644 --- a/backend/app/services/turnstile_service.py +++ b/backend/app/services/turnstile_service.py @@ -15,7 +15,7 @@ TURNSTILE_SITEVERIFY_URL = "https://challenges.cloudflare.com/turnstile/v0/siteverify" DEFAULT_TURNSTILE_ACTION = "geoportal_gate" -DEFAULT_TURNSTILE_COOKIE_NAME = "btaa_turnstile_session" +DEFAULT_TURNSTILE_COOKIE_NAME = "ogm_turnstile_session" DEFAULT_TURNSTILE_SESSION_TTL_SECONDS = 3600 diff --git a/backend/app/tasks/worker.py b/backend/app/tasks/worker.py index 2404168..810bb6e 100644 --- a/backend/app/tasks/worker.py +++ b/backend/app/tasks/worker.py @@ -316,7 +316,7 @@ def fetch_and_cache_image(self, url: str, doc_id: Optional[str] = None) -> bool: # Use User-Agent header to avoid 403 errors from servers that block bots # Some ArcGIS ImageServer exportImage URLs can take 15-30s when server is cold fetch_timeout = int(os.getenv("THUMBNAIL_FETCH_TIMEOUT", "30")) - headers = {"User-Agent": "BTAA-Geospatial-Data-API/1.0 (https://geo.btaa.org/)"} + headers = {"User-Agent": "OpenGeoMetadata-API/1.0 (https://opengeometadata.org/)"} with provider_request_slot(resolved_url, action="thumbnail fetch") as lease: fetched = fetch_public_http_bytes( resolved_url, @@ -613,7 +613,7 @@ def _render_pdf_first_page(pdf_bytes: bytes) -> Optional[bytes]: def _generate_pdf_thumbnail_bytes(pdf_url: str) -> Optional[bytes]: """Fetch a public, bounded PDF and render its first page to PNG.""" - headers = {"User-Agent": "BTAA-Geospatial-Data-API/1.0 (https://geo.btaa.org/)"} + headers = {"User-Agent": "OpenGeoMetadata-API/1.0 (https://opengeometadata.org/)"} fetched = fetch_public_http_bytes( pdf_url, timeout=int(os.getenv("THUMBNAIL_FETCH_TIMEOUT", "30")), @@ -931,7 +931,7 @@ def get_bytes(offset: int, length: int) -> bytes: fetch_len = max(length, 512) end = offset + fetch_len - 1 headers = { - "User-Agent": "BTAA-Geospatial-Data-API/1.0 (https://geo.btaa.org/)", + "User-Agent": "OpenGeoMetadata-API/1.0 (https://opengeometadata.org/)", "Range": f"bytes={offset}-{end}", # Avoid gzip: range of gzip stream is not independently decompressible. "Accept-Encoding": "identity", diff --git a/backend/conftest.py b/backend/conftest.py index 21fd863..ac82c64 100644 --- a/backend/conftest.py +++ b/backend/conftest.py @@ -71,7 +71,7 @@ # Configure tests to piggy-back on primary Docker services with isolated test DB/indices # Default to the main compose ports (ParadeDB 2345, ES 9200, Redis 6379) -TEST_DB_NAME = os.getenv("TEST_DB_NAME", "btaa_geospatial_api_test") +TEST_DB_NAME = os.getenv("TEST_DB_NAME", "opengeometadata_api_test") DB_USER = os.getenv("DB_USER", "postgres") # Check POSTGRES_PASSWORD first (from .env), then DB_PASSWORD, then default to "postgres" DB_PASSWORD = os.getenv("POSTGRES_PASSWORD") or os.getenv("DB_PASSWORD", "postgres") @@ -90,8 +90,8 @@ parsed = urlparse(DATABASE_URL) docker_hostnames = [ "paradedb", - "btaa-geospatial-api-paradedb", - "btaa-geospatial-api-paradedb-1", + "opengeometadata-api-paradedb", + "opengeometadata-api-paradedb-1", ] if parsed.hostname in docker_hostnames: # Replace Docker hostname with localhost and use port 2345 (Docker mapped port) @@ -127,7 +127,7 @@ _HB_STOP = threading.Event() _HB_THREAD: threading.Thread | None = None _HB_PATH: Path | None = None -SKIP_TEST_DATABASE = os.getenv("BTAA_SKIP_TEST_DB", "").strip().lower() in { +SKIP_TEST_DATABASE = os.getenv("OGM_SKIP_TEST_DB", "").strip().lower() in { "1", "true", "yes", @@ -228,7 +228,7 @@ def pytest_configure(config): "localhost", "127.0.0.1", "elasticsearch", - "btaa-geospatial-api-elasticsearch", + "opengeometadata-api-elasticsearch", "http://localhost:9200", "http://127.0.0.1:9200", ) @@ -239,9 +239,9 @@ def pytest_configure(config): ) # Force a test-specific index name unless explicitly overridden to another *_test index. - es_index = os.getenv("ELASTICSEARCH_INDEX", "btaa_geospatial_api_test") + es_index = os.getenv("ELASTICSEARCH_INDEX", "opengeometadata_api_test") if not es_index.endswith("_test"): - es_index = "btaa_geospatial_api_test" + es_index = "opengeometadata_api_test" os.environ["ELASTICSEARCH_INDEX"] = es_index # Isolate Redis usage to a separate logical DB during tests diff --git a/backend/data/fixtures/download_b1g_fixtures.py b/backend/data/fixtures/download_b1g_fixtures.py index 9d206fc..f46eb05 100644 --- a/backend/data/fixtures/download_b1g_fixtures.py +++ b/backend/data/fixtures/download_b1g_fixtures.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """ -Download BTAA fixture files that have "b1g" in their URLs. +Download OGM fixture files that have "b1g" in their URLs. Appends /raw to each link and downloads the files. """ @@ -96,8 +96,8 @@ def download_file(url, output_dir, index): def main(): # Get the directory where this script is located script_dir = Path(__file__).parent - csv_file = script_dir / "btaa_fixtures_list.csv" - output_dir = script_dir / "btaa_fixtures_data" + csv_file = script_dir / "ogm_fixtures_list.csv" + output_dir = script_dir / "ogm_fixtures_data" # Create output directory output_dir.mkdir(exist_ok=True) diff --git a/backend/data/fixtures/download_btaa_fixtures.py b/backend/data/fixtures/download_ogm_fixtures.py similarity index 96% rename from backend/data/fixtures/download_btaa_fixtures.py rename to backend/data/fixtures/download_ogm_fixtures.py index 7a1d886..d33b1ac 100644 --- a/backend/data/fixtures/download_btaa_fixtures.py +++ b/backend/data/fixtures/download_ogm_fixtures.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """ -Download BTAA fixture files from the links in btaa_fixtures_list.csv +Download OGM fixture files from the links in ogm_fixtures_list.csv Appends /raw to each link and downloads the files. """ @@ -97,8 +97,8 @@ def download_file(url, output_dir, index): def main(): # Get the directory where this script is located script_dir = Path(__file__).parent - csv_file = script_dir / "btaa_fixtures_list.csv" - output_dir = script_dir / "btaa_fixtures_data" + csv_file = script_dir / "ogm_fixtures_list.csv" + output_dir = script_dir / "ogm_fixtures_data" # Create output directory output_dir.mkdir(exist_ok=True) diff --git a/backend/data/fixtures/btaa_featured_resources/ee251fd05e504374831ab4ddf5e589f2_2.json b/backend/data/fixtures/ogm_featured_resources/ee251fd05e504374831ab4ddf5e589f2_2.json similarity index 100% rename from backend/data/fixtures/btaa_featured_resources/ee251fd05e504374831ab4ddf5e589f2_2.json rename to backend/data/fixtures/ogm_featured_resources/ee251fd05e504374831ab4ddf5e589f2_2.json diff --git a/backend/data/fixtures/btaa_fixtures_data/018b1db0-726a-4727-af0a-5c7e18783ace.json b/backend/data/fixtures/ogm_fixtures_data/018b1db0-726a-4727-af0a-5c7e18783ace.json similarity index 100% rename from backend/data/fixtures/btaa_fixtures_data/018b1db0-726a-4727-af0a-5c7e18783ace.json rename to backend/data/fixtures/ogm_fixtures_data/018b1db0-726a-4727-af0a-5c7e18783ace.json diff --git a/backend/data/fixtures/btaa_fixtures_data/09a-04.json b/backend/data/fixtures/ogm_fixtures_data/09a-04.json similarity index 100% rename from backend/data/fixtures/btaa_fixtures_data/09a-04.json rename to backend/data/fixtures/ogm_fixtures_data/09a-04.json diff --git a/backend/data/fixtures/btaa_fixtures_data/0b75937e-2f44-4e49-bd1e-3e3adbed6f84.json b/backend/data/fixtures/ogm_fixtures_data/0b75937e-2f44-4e49-bd1e-3e3adbed6f84.json similarity index 100% rename from backend/data/fixtures/btaa_fixtures_data/0b75937e-2f44-4e49-bd1e-3e3adbed6f84.json rename to backend/data/fixtures/ogm_fixtures_data/0b75937e-2f44-4e49-bd1e-3e3adbed6f84.json diff --git a/backend/data/fixtures/btaa_fixtures_data/14E37141-2059-462E-ABCA-9628E3BFB636.json b/backend/data/fixtures/ogm_fixtures_data/14E37141-2059-462E-ABCA-9628E3BFB636.json similarity index 100% rename from backend/data/fixtures/btaa_fixtures_data/14E37141-2059-462E-ABCA-9628E3BFB636.json rename to backend/data/fixtures/ogm_fixtures_data/14E37141-2059-462E-ABCA-9628E3BFB636.json diff --git a/backend/data/fixtures/btaa_fixtures_data/16465B6B-742A-4335-BBF5-C4F7EC1BA9D4.json b/backend/data/fixtures/ogm_fixtures_data/16465B6B-742A-4335-BBF5-C4F7EC1BA9D4.json similarity index 100% rename from backend/data/fixtures/btaa_fixtures_data/16465B6B-742A-4335-BBF5-C4F7EC1BA9D4.json rename to backend/data/fixtures/ogm_fixtures_data/16465B6B-742A-4335-BBF5-C4F7EC1BA9D4.json diff --git a/backend/data/fixtures/btaa_fixtures_data/219ffed3-3e58-4fb7-ad82-4c264aae1b17.json b/backend/data/fixtures/ogm_fixtures_data/219ffed3-3e58-4fb7-ad82-4c264aae1b17.json similarity index 100% rename from backend/data/fixtures/btaa_fixtures_data/219ffed3-3e58-4fb7-ad82-4c264aae1b17.json rename to backend/data/fixtures/ogm_fixtures_data/219ffed3-3e58-4fb7-ad82-4c264aae1b17.json diff --git a/backend/data/fixtures/btaa_fixtures_data/219ffed3-3e58-4fb7-ad82-4c264aae1b17_1.json b/backend/data/fixtures/ogm_fixtures_data/219ffed3-3e58-4fb7-ad82-4c264aae1b17_1.json similarity index 100% rename from backend/data/fixtures/btaa_fixtures_data/219ffed3-3e58-4fb7-ad82-4c264aae1b17_1.json rename to backend/data/fixtures/ogm_fixtures_data/219ffed3-3e58-4fb7-ad82-4c264aae1b17_1.json diff --git a/backend/data/fixtures/btaa_fixtures_data/2787a9e8-0bef-452e-9e5f-e83042c193b4.json b/backend/data/fixtures/ogm_fixtures_data/2787a9e8-0bef-452e-9e5f-e83042c193b4.json similarity index 100% rename from backend/data/fixtures/btaa_fixtures_data/2787a9e8-0bef-452e-9e5f-e83042c193b4.json rename to backend/data/fixtures/ogm_fixtures_data/2787a9e8-0bef-452e-9e5f-e83042c193b4.json diff --git a/backend/data/fixtures/btaa_fixtures_data/4979dd07507f4155bb92689860dd5089.json b/backend/data/fixtures/ogm_fixtures_data/4979dd07507f4155bb92689860dd5089.json similarity index 100% rename from backend/data/fixtures/btaa_fixtures_data/4979dd07507f4155bb92689860dd5089.json rename to backend/data/fixtures/ogm_fixtures_data/4979dd07507f4155bb92689860dd5089.json diff --git a/backend/data/fixtures/btaa_fixtures_data/4B758FE6-D2B5-463D-8E25-502CB4D90376.json b/backend/data/fixtures/ogm_fixtures_data/4B758FE6-D2B5-463D-8E25-502CB4D90376.json similarity index 100% rename from backend/data/fixtures/btaa_fixtures_data/4B758FE6-D2B5-463D-8E25-502CB4D90376.json rename to backend/data/fixtures/ogm_fixtures_data/4B758FE6-D2B5-463D-8E25-502CB4D90376.json diff --git a/backend/data/fixtures/btaa_fixtures_data/4cd9f01beba64dce9e8502a7924d0deb_0.json b/backend/data/fixtures/ogm_fixtures_data/4cd9f01beba64dce9e8502a7924d0deb_0.json similarity index 100% rename from backend/data/fixtures/btaa_fixtures_data/4cd9f01beba64dce9e8502a7924d0deb_0.json rename to backend/data/fixtures/ogm_fixtures_data/4cd9f01beba64dce9e8502a7924d0deb_0.json diff --git a/backend/data/fixtures/btaa_fixtures_data/502D1D34-FDB0-456E-BD4A-73299B9C2E5F.json b/backend/data/fixtures/ogm_fixtures_data/502D1D34-FDB0-456E-BD4A-73299B9C2E5F.json similarity index 100% rename from backend/data/fixtures/btaa_fixtures_data/502D1D34-FDB0-456E-BD4A-73299B9C2E5F.json rename to backend/data/fixtures/ogm_fixtures_data/502D1D34-FDB0-456E-BD4A-73299B9C2E5F.json diff --git a/backend/data/fixtures/btaa_fixtures_data/8888-002.json b/backend/data/fixtures/ogm_fixtures_data/8888-002.json similarity index 100% rename from backend/data/fixtures/btaa_fixtures_data/8888-002.json rename to backend/data/fixtures/ogm_fixtures_data/8888-002.json diff --git a/backend/data/fixtures/btaa_fixtures_data/88be737b-4fea-4f23-9433-a008ed6b18b5.json b/backend/data/fixtures/ogm_fixtures_data/88be737b-4fea-4f23-9433-a008ed6b18b5.json similarity index 100% rename from backend/data/fixtures/btaa_fixtures_data/88be737b-4fea-4f23-9433-a008ed6b18b5.json rename to backend/data/fixtures/ogm_fixtures_data/88be737b-4fea-4f23-9433-a008ed6b18b5.json diff --git a/backend/data/fixtures/btaa_fixtures_data/999-0003-007.json b/backend/data/fixtures/ogm_fixtures_data/999-0003-007.json similarity index 100% rename from backend/data/fixtures/btaa_fixtures_data/999-0003-007.json rename to backend/data/fixtures/ogm_fixtures_data/999-0003-007.json diff --git a/backend/data/fixtures/btaa_fixtures_data/999-0011-california.json b/backend/data/fixtures/ogm_fixtures_data/999-0011-california.json similarity index 100% rename from backend/data/fixtures/btaa_fixtures_data/999-0011-california.json rename to backend/data/fixtures/ogm_fixtures_data/999-0011-california.json diff --git a/backend/data/fixtures/btaa_fixtures_data/9eccb622-8fe3-4f94-9a5c-e166585eb597.json b/backend/data/fixtures/ogm_fixtures_data/9eccb622-8fe3-4f94-9a5c-e166585eb597.json similarity index 100% rename from backend/data/fixtures/btaa_fixtures_data/9eccb622-8fe3-4f94-9a5c-e166585eb597.json rename to backend/data/fixtures/ogm_fixtures_data/9eccb622-8fe3-4f94-9a5c-e166585eb597.json diff --git a/backend/data/fixtures/btaa_fixtures_data/CityOfWaukesha-3eea70a5e4af40a1a558a43705ff8596.json b/backend/data/fixtures/ogm_fixtures_data/CityOfWaukesha-3eea70a5e4af40a1a558a43705ff8596.json similarity index 100% rename from backend/data/fixtures/btaa_fixtures_data/CityOfWaukesha-3eea70a5e4af40a1a558a43705ff8596.json rename to backend/data/fixtures/ogm_fixtures_data/CityOfWaukesha-3eea70a5e4af40a1a558a43705ff8596.json diff --git a/backend/data/fixtures/btaa_fixtures_data/VAC9619-000022.json b/backend/data/fixtures/ogm_fixtures_data/VAC9619-000022.json similarity index 100% rename from backend/data/fixtures/btaa_fixtures_data/VAC9619-000022.json rename to backend/data/fixtures/ogm_fixtures_data/VAC9619-000022.json diff --git a/backend/data/fixtures/btaa_fixtures_data/b1e04fea-8a02-426d-94c1-0897707fa563.json b/backend/data/fixtures/ogm_fixtures_data/b1e04fea-8a02-426d-94c1-0897707fa563.json similarity index 100% rename from backend/data/fixtures/btaa_fixtures_data/b1e04fea-8a02-426d-94c1-0897707fa563.json rename to backend/data/fixtures/ogm_fixtures_data/b1e04fea-8a02-426d-94c1-0897707fa563.json diff --git a/backend/data/fixtures/btaa_fixtures_data/b1g_BtbnzIbFhMiC.json b/backend/data/fixtures/ogm_fixtures_data/b1g_BtbnzIbFhMiC.json similarity index 100% rename from backend/data/fixtures/btaa_fixtures_data/b1g_BtbnzIbFhMiC.json rename to backend/data/fixtures/ogm_fixtures_data/b1g_BtbnzIbFhMiC.json diff --git a/backend/data/fixtures/btaa_fixtures_data/b1g_Jeks5eSaDHp5.json b/backend/data/fixtures/ogm_fixtures_data/b1g_Jeks5eSaDHp5.json similarity index 100% rename from backend/data/fixtures/btaa_fixtures_data/b1g_Jeks5eSaDHp5.json rename to backend/data/fixtures/ogm_fixtures_data/b1g_Jeks5eSaDHp5.json diff --git a/backend/data/fixtures/btaa_fixtures_data/b1g_PJxxfKgpqpUT.json b/backend/data/fixtures/ogm_fixtures_data/b1g_PJxxfKgpqpUT.json similarity index 100% rename from backend/data/fixtures/btaa_fixtures_data/b1g_PJxxfKgpqpUT.json rename to backend/data/fixtures/ogm_fixtures_data/b1g_PJxxfKgpqpUT.json diff --git a/backend/data/fixtures/btaa_fixtures_data/cugir-007739.json b/backend/data/fixtures/ogm_fixtures_data/cugir-007739.json similarity index 100% rename from backend/data/fixtures/btaa_fixtures_data/cugir-007739.json rename to backend/data/fixtures/ogm_fixtures_data/cugir-007739.json diff --git a/backend/data/fixtures/btaa_fixtures_data/cugir-007739_1.json b/backend/data/fixtures/ogm_fixtures_data/cugir-007739_1.json similarity index 100% rename from backend/data/fixtures/btaa_fixtures_data/cugir-007739_1.json rename to backend/data/fixtures/ogm_fixtures_data/cugir-007739_1.json diff --git a/backend/data/fixtures/btaa_fixtures_data/d128aa1618744699be00be0e494d01de_0.json b/backend/data/fixtures/ogm_fixtures_data/d128aa1618744699be00be0e494d01de_0.json similarity index 100% rename from backend/data/fixtures/btaa_fixtures_data/d128aa1618744699be00be0e494d01de_0.json rename to backend/data/fixtures/ogm_fixtures_data/d128aa1618744699be00be0e494d01de_0.json diff --git a/backend/data/fixtures/btaa_fixtures_data/p16022coll230_2937.json b/backend/data/fixtures/ogm_fixtures_data/p16022coll230_2937.json similarity index 100% rename from backend/data/fixtures/btaa_fixtures_data/p16022coll230_2937.json rename to backend/data/fixtures/ogm_fixtures_data/p16022coll230_2937.json diff --git a/backend/data/fixtures/btaa_fixtures_data/p16022coll230_3666.json b/backend/data/fixtures/ogm_fixtures_data/p16022coll230_3666.json similarity index 100% rename from backend/data/fixtures/btaa_fixtures_data/p16022coll230_3666.json rename to backend/data/fixtures/ogm_fixtures_data/p16022coll230_3666.json diff --git a/backend/data/fixtures/btaa_fixtures_data/p16022coll230_3666_1.json b/backend/data/fixtures/ogm_fixtures_data/p16022coll230_3666_1.json similarity index 100% rename from backend/data/fixtures/btaa_fixtures_data/p16022coll230_3666_1.json rename to backend/data/fixtures/ogm_fixtures_data/p16022coll230_3666_1.json diff --git a/backend/data/fixtures/btaa_fixtures_data/p16022coll231_2412.json b/backend/data/fixtures/ogm_fixtures_data/p16022coll231_2412.json similarity index 100% rename from backend/data/fixtures/btaa_fixtures_data/p16022coll231_2412.json rename to backend/data/fixtures/ogm_fixtures_data/p16022coll231_2412.json diff --git a/backend/data/fixtures/btaa_fixtures_data/pstems_0052767067_brownsville_08_pitt.json b/backend/data/fixtures/ogm_fixtures_data/pstems_0052767067_brownsville_08_pitt.json similarity index 100% rename from backend/data/fixtures/btaa_fixtures_data/pstems_0052767067_brownsville_08_pitt.json rename to backend/data/fixtures/ogm_fixtures_data/pstems_0052767067_brownsville_08_pitt.json diff --git a/backend/data/fixtures/btaa_fixtures_data/rutgers-lib_35507.json b/backend/data/fixtures/ogm_fixtures_data/rutgers-lib_35507.json similarity index 100% rename from backend/data/fixtures/btaa_fixtures_data/rutgers-lib_35507.json rename to backend/data/fixtures/ogm_fixtures_data/rutgers-lib_35507.json diff --git a/backend/data/fixtures/btaa_fixtures_data/stanford-bs024ty5255.json b/backend/data/fixtures/ogm_fixtures_data/stanford-bs024ty5255.json similarity index 100% rename from backend/data/fixtures/btaa_fixtures_data/stanford-bs024ty5255.json rename to backend/data/fixtures/ogm_fixtures_data/stanford-bs024ty5255.json diff --git a/backend/data/fixtures/btaa_fixtures_data/utaustin_121171.html b/backend/data/fixtures/ogm_fixtures_data/utaustin_121171.html similarity index 100% rename from backend/data/fixtures/btaa_fixtures_data/utaustin_121171.html rename to backend/data/fixtures/ogm_fixtures_data/utaustin_121171.html diff --git a/backend/data/btaa_geospatial_api.txt b/backend/data/ogm_api_legacy_dump.txt similarity index 100% rename from backend/data/btaa_geospatial_api.txt rename to backend/data/ogm_api_legacy_dump.txt diff --git a/backend/data/parade_db_gbl_table.py b/backend/data/parade_db_gbl_table.py index 9131119..59f49a9 100644 --- a/backend/data/parade_db_gbl_table.py +++ b/backend/data/parade_db_gbl_table.py @@ -37,7 +37,7 @@ # Connect to PostgreSQL using environment variables conn = psycopg2.connect( - dbname=os.getenv("POSTGRES_DB", "btaa_ogm_api"), + dbname=os.getenv("POSTGRES_DB", "opengeometadata_api"), user=os.getenv("POSTGRES_USER", "postgres"), password=os.getenv("POSTGRES_PASSWORD", "postgres"), host=os.getenv("POSTGRES_HOST", "paradedb"), # Use the Docker service name diff --git a/backend/db/config.py b/backend/db/config.py index 9104a3d..2fb5202 100644 --- a/backend/db/config.py +++ b/backend/db/config.py @@ -42,10 +42,10 @@ def _repair_placeholder_database_password(database_url: str | None) -> str | Non is_docker = os.getenv("IS_DOCKER") == "true" DB_HOST = os.getenv("DB_HOST", "localhost" if not is_docker else "paradedb") DB_PORT = os.getenv("DB_PORT", "2345" if not is_docker else "5432") - # Always default to the btaa_geospatial_api database for this application - DB_NAME = os.getenv("DB_NAME", "btaa_geospatial_api") + # Always default to the opengeometadata_api database for this application + DB_NAME = os.getenv("DB_NAME", "opengeometadata_api") - # Construct database URL with asyncpg driver, always targeting btaa_geospatial_api + # Construct database URL with asyncpg driver, always targeting opengeometadata_api DATABASE_URL = f"postgresql+asyncpg://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}" else: DATABASE_URL = _repair_placeholder_database_password(DATABASE_URL) @@ -57,8 +57,8 @@ def _repair_placeholder_database_password(database_url: str | None) -> str | Non parsed = urlparse(DATABASE_URL) docker_hostnames = [ "paradedb", - "btaa-geospatial-api-paradedb", - "btaa-geospatial-api-paradedb-1", + "opengeometadata-api-paradedb", + "opengeometadata-api-paradedb-1", ] if parsed.hostname in docker_hostnames: # Replace Docker hostname with localhost and use port 2345 (Docker mapped port) diff --git a/backend/db/create_tables.py b/backend/db/create_tables.py index 1f5dba5..b33b0a5 100644 --- a/backend/db/create_tables.py +++ b/backend/db/create_tables.py @@ -10,7 +10,7 @@ # Get the database URL DATABASE_URL = os.getenv( - "DATABASE_URL", "postgresql://postgres:postgres@paradedb:5432/btaa_ogm_api" + "DATABASE_URL", "postgresql://postgres:postgres@paradedb:5432/opengeometadata_api" ) diff --git a/backend/db/migrations/add_api_keys_allowed_ips.py b/backend/db/migrations/add_api_keys_allowed_ips.py index 1f293bd..753bd2b 100644 --- a/backend/db/migrations/add_api_keys_allowed_ips.py +++ b/backend/db/migrations/add_api_keys_allowed_ips.py @@ -31,18 +31,18 @@ def add_api_keys_allowed_ips_column(): """Add allowed_ips JSON column to api_keys table.""" try: # Get database URL from environment and ensure it's synchronous - database_url = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@localhost:2345/btaa_ogm_api_test") + database_url = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@localhost:2345/opengeometadata_api_test") sync_database_url = database_url.replace("postgresql+asyncpg://", "postgresql://") # Handle Docker hostnames for local development (only if NOT running in Docker) is_docker = os.getenv("IS_DOCKER", "false").lower() == "true" if not is_docker: parsed = urlparse(sync_database_url) - if parsed.hostname and ("paradedb" in parsed.hostname or "btaa-geospatial-api" in parsed.hostname): + if parsed.hostname and "paradedb" in parsed.hostname: local_port = os.getenv("DB_PORT", "2345") local_user = os.getenv("DB_USER", "postgres") local_password = os.getenv("DB_PASSWORD", "postgres") - local_db = os.getenv("DB_NAME", parsed.path.lstrip("/") if parsed.path else "btaa_geospatial_api") + local_db = os.getenv("DB_NAME", parsed.path.lstrip("/") if parsed.path else "opengeometadata_api") new_netloc = f"{local_user}:{local_password}@localhost:{local_port}" sync_database_url = urlunparse(parsed._replace(netloc=new_netloc, path=f"/{local_db}")) @@ -78,4 +78,3 @@ def add_api_keys_allowed_ips_column(): if __name__ == "__main__": add_api_keys_allowed_ips_column() - diff --git a/backend/db/migrations/add_enrichment_type.py b/backend/db/migrations/add_enrichment_type.py index 822d0ad..e62128f 100644 --- a/backend/db/migrations/add_enrichment_type.py +++ b/backend/db/migrations/add_enrichment_type.py @@ -22,7 +22,7 @@ def add_enrichment_type_column(): """Add enrichment_type column to ai_enrichments table.""" try: # Get database URL from environment and ensure it's synchronous - database_url = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@localhost:2345/btaa_ogm_api_test") + database_url = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@localhost:2345/opengeometadata_api_test") sync_database_url = database_url.replace("postgresql+asyncpg://", "postgresql://") # Create engine diff --git a/backend/db/migrations/add_latest_btaa_schema_fields.py b/backend/db/migrations/add_latest_ogm_schema_fields.py similarity index 81% rename from backend/db/migrations/add_latest_btaa_schema_fields.py rename to backend/db/migrations/add_latest_ogm_schema_fields.py index ee9145c..2decfd0 100644 --- a/backend/db/migrations/add_latest_btaa_schema_fields.py +++ b/backend/db/migrations/add_latest_ogm_schema_fields.py @@ -13,16 +13,16 @@ logger = logging.getLogger(__name__) -def add_latest_btaa_schema_fields(): +def add_latest_ogm_schema_fields(): """ - Add latest BTAA schema compatibility fields to the resources table. + Add latest OGM schema compatibility fields to the resources table. This migration is idempotent and safe to re-run. """ try: database_url = os.getenv( "DATABASE_URL", - "postgresql+asyncpg://postgres:postgres@localhost:2345/btaa_ogm_api", + "postgresql+asyncpg://postgres:postgres@localhost:2345/opengeometadata_api", ) sync_database_url = database_url.replace("postgresql+asyncpg://", "postgresql://") @@ -30,12 +30,12 @@ def add_latest_btaa_schema_fields(): inspector = inspect(engine) if not inspector.has_table("resources"): - logger.error("Resources table does not exist. Cannot add latest BTAA fields.") + logger.error("Resources table does not exist. Cannot add latest OGM fields.") return - # Field names/types requested for latest BTAA schema support. + # Field names/types requested for latest OGM schema support. # Mixed-case names are quoted to preserve exact column casing. - latest_btaa_fields = [ + latest_ogm_fields = [ ("b1g_adminNote_sm", "VARCHAR[]"), ("b1g_dateAccessioned_dt", "TIMESTAMP"), ("b1g_dateRetired_dt", "TIMESTAMP"), @@ -49,7 +49,7 @@ def add_latest_btaa_schema_fields(): ] with engine.connect() as conn: - for field_name, field_type in latest_btaa_fields: + for field_name, field_type in latest_ogm_fields: try: conn.execute( text(f'ALTER TABLE resources ADD COLUMN "{field_name}" {field_type}') @@ -64,12 +64,12 @@ def add_latest_btaa_schema_fields(): else: logger.warning(f"Could not add column {field_name}: {e}") - logger.info("Successfully added latest BTAA schema fields.") + logger.info("Successfully added latest OGM schema fields.") except Exception as e: - logger.error(f"Error adding latest BTAA schema fields: {e}") + logger.error(f"Error adding latest OGM schema fields: {e}") raise if __name__ == "__main__": - add_latest_btaa_schema_fields() + add_latest_ogm_schema_fields() diff --git a/backend/db/migrations/add_missing_fields_for_migration.py b/backend/db/migrations/add_missing_fields_for_migration.py index be02579..27ee0bf 100644 --- a/backend/db/migrations/add_missing_fields_for_migration.py +++ b/backend/db/migrations/add_missing_fields_for_migration.py @@ -22,7 +22,7 @@ def add_missing_fields_for_migration(): """ try: # Get database URL from environment and ensure it's synchronous - database_url = os.getenv("DATABASE_URL", "postgresql+asyncpg://postgres:postgres@localhost:2345/btaa_ogm_api") + database_url = os.getenv("DATABASE_URL", "postgresql+asyncpg://postgres:postgres@localhost:2345/opengeometadata_api") sync_database_url = database_url.replace("postgresql+asyncpg://", "postgresql://") # Create engine @@ -37,7 +37,7 @@ def add_missing_fields_for_migration(): with engine.connect() as conn: # List of additional fields to add additional_fields = [ - # BTAA-specific fields found in old database + # OGM-specific fields found in old database ("b1g_adms_supportedSchema_sm", "VARCHAR[]"), ("b1g_dateAccessioned_sm", "VARCHAR[]"), # Array version for migration compatibility ("b1g_dcat_endpointDescription_s", "VARCHAR"), diff --git a/backend/db/migrations/add_btaa_ogm_fields.py b/backend/db/migrations/add_ogm_fields.py similarity index 85% rename from backend/db/migrations/add_btaa_ogm_fields.py rename to backend/db/migrations/add_ogm_fields.py index 3c51acc..43db813 100644 --- a/backend/db/migrations/add_btaa_ogm_fields.py +++ b/backend/db/migrations/add_ogm_fields.py @@ -13,11 +13,11 @@ logger = logging.getLogger(__name__) -def add_btaa_ogm_fields(): - """Add BTAA-specific OGM Aardvark fields to the resources table.""" +def add_ogm_fields(): + """Add OGM-specific OGM Aardvark fields to the resources table.""" try: # Get database URL from environment and ensure it's synchronous - database_url = os.getenv("DATABASE_URL", "postgresql+asyncpg://postgres:postgres@localhost:2345/btaa_ogm_api") + database_url = os.getenv("DATABASE_URL", "postgresql+asyncpg://postgres:postgres@localhost:2345/opengeometadata_api") sync_database_url = database_url.replace("postgresql+asyncpg://", "postgresql://") # Create engine @@ -26,12 +26,12 @@ def add_btaa_ogm_fields(): # Check if the table exists if not inspector.has_table("resources"): - logger.error("Resources table does not exist. Cannot add BTAA fields.") + logger.error("Resources table does not exist. Cannot add OGM fields.") return with engine.connect() as conn: - # List of BTAA fields to add - btaa_fields = [ + # List of OGM fields to add + ogm_fields = [ ("b1g_code_s", "VARCHAR"), ("b1g_status_s", "VARCHAR"), ("b1g_dct_accrualmethod_s", "VARCHAR"), @@ -54,7 +54,7 @@ def add_btaa_ogm_fields(): ] # Add each field if it doesn't exist - for field_name, field_type in btaa_fields: + for field_name, field_type in ogm_fields: try: # Try to add the column - PostgreSQL will error if it already exists conn.execute(text(f"ALTER TABLE resources ADD COLUMN {field_name} {field_type}")) @@ -66,12 +66,12 @@ def add_btaa_ogm_fields(): logger.warning(f"Could not add column {field_name}: {e}") conn.commit() - logger.info("Successfully added BTAA OGM fields to resources table.") + logger.info("Successfully added OGM fields to resources table.") except Exception as e: - logger.error(f"Error adding BTAA OGM fields: {e}") + logger.error(f"Error adding OGM fields: {e}") raise if __name__ == "__main__": - add_btaa_ogm_fields() + add_ogm_fields() diff --git a/backend/db/migrations/backfill_resources_from_legacy_items.py b/backend/db/migrations/backfill_resources_from_legacy_items.py index f7e82e8..e073741 100644 --- a/backend/db/migrations/backfill_resources_from_legacy_items.py +++ b/backend/db/migrations/backfill_resources_from_legacy_items.py @@ -96,7 +96,7 @@ def build_backfill_statement(): def backfill_resources_from_legacy_items(): """Copy legacy `items` rows into `resources` without touching existing rows.""" database_url = os.getenv( - "DATABASE_URL", "postgresql+asyncpg://postgres:postgres@localhost:2345/btaa_ogm_api" + "DATABASE_URL", "postgresql+asyncpg://postgres:postgres@localhost:2345/opengeometadata_api" ) engine = create_engine(_normalize_database_url(database_url)) inspector = inspect(engine) diff --git a/backend/db/migrations/bridge_old_production.py b/backend/db/migrations/bridge_old_production.py index 6af9e57..86afd4c 100644 --- a/backend/db/migrations/bridge_old_production.py +++ b/backend/db/migrations/bridge_old_production.py @@ -50,7 +50,7 @@ # Identifiers and metadata "dct_identifier_sm", "gbl_mdModified_dt", "gbl_mdVersion_s", "gbl_suppressed_b", "gbl_georeferenced_b", - # BTAA-specific fields + # OGM-specific fields "b1g_code_s", "b1g_status_s", "b1g_dct_accrualMethod_s", "b1g_dct_accrualPeriodicity_s", "b1g_dateAccessioned_s", "b1g_dateAccessioned_sm", "b1g_dateRetired_s", "b1g_child_record_b", "b1g_dct_mediator_sm", "b1g_access_s", "b1g_image_ss", "b1g_geonames_sm", @@ -58,12 +58,12 @@ "b1g_dcat_spatialResolutionInMeters_sm", "b1g_geodcat_spatialResolutionAsText_sm", "b1g_dct_provenanceStatement_sm", "b1g_adminTags_sm", - # Additional BTAA fields for migration + # Additional OGM fields for migration "b1g_adms_supportedSchema_sm", "b1g_dcat_endpointDescription_s", "b1g_dcat_endpointURL_s", "b1g_dcat_inSeries_sm", "b1g_localCollectionLabel_sm", "b1g_prov_softwareAgent_sm", "b1g_prov_wasGeneratedBy_sm", "date_created_dtsi", "date_modified_dtsi", "geomg_id_s", "publication_state", "import_id", - # BTAA latest-schema compatibility fields kept in the bridge view + # OGM latest-schema compatibility fields kept in the bridge view "b1g_adminNote_sm", "b1g_dateAccessioned_dt", "b1g_dateRetired_dt", "b1g_deprioritized_b", "b1g_harvestWorkflow_s", "b1g_isHarvested_b", "b1g_lastHarvested_dt", "b1g_dct_provenance_sm", "b1g_dcat_spatialResolutionInMeters_s", "b1g_websitePlatform_s", diff --git a/backend/db/migrations/create_ai_enrichments.py b/backend/db/migrations/create_ai_enrichments.py index 36f443f..343fde1 100644 --- a/backend/db/migrations/create_ai_enrichments.py +++ b/backend/db/migrations/create_ai_enrichments.py @@ -19,7 +19,7 @@ def create_ai_enrichments_table(): """Create the resource_ai_enrichments table.""" try: # Get database URL from environment and ensure it's synchronous - database_url = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@localhost:2345/btaa_ogm_api_test") + database_url = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@localhost:2345/opengeometadata_api_test") sync_database_url = database_url.replace("postgresql+asyncpg://", "postgresql://") # Create engine diff --git a/backend/db/migrations/create_api_rate_limiting_tables.py b/backend/db/migrations/create_api_rate_limiting_tables.py index 9bac5f0..17c67c3 100644 --- a/backend/db/migrations/create_api_rate_limiting_tables.py +++ b/backend/db/migrations/create_api_rate_limiting_tables.py @@ -37,7 +37,7 @@ def create_api_rate_limiting_tables(): """Create the API rate limiting tables.""" try: # Get database URL from environment and ensure it's synchronous - database_url = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@localhost:2345/btaa_ogm_api_test") + database_url = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@localhost:2345/opengeometadata_api_test") # Convert asyncpg URL to sync URL sync_database_url = database_url.replace("postgresql+asyncpg://", "postgresql://") @@ -46,13 +46,13 @@ def create_api_rate_limiting_tables(): # If running locally (not in Docker) and DATABASE_URL points to a Docker service, convert to localhost:2345 parsed = urlparse(sync_database_url) is_docker = os.getenv("IS_DOCKER", "false").lower() == "true" - if not is_docker and parsed.hostname and ("paradedb" in parsed.hostname or "btaa-geospatial-api" in parsed.hostname): + if not is_docker and parsed.hostname and "paradedb" in parsed.hostname: # Replace Docker hostname with localhost and use port 2345 for local development # Use local database credentials from environment or defaults local_port = os.getenv("DB_PORT", "2345") local_user = os.getenv("DB_USER", "postgres") local_password = os.getenv("DB_PASSWORD", "postgres") - local_db = os.getenv("DB_NAME", parsed.path.lstrip("/") if parsed.path else "btaa_geospatial_api") + local_db = os.getenv("DB_NAME", parsed.path.lstrip("/") if parsed.path else "opengeometadata_api") # Build new netloc with local credentials new_netloc = f"{local_user}:{local_password}@localhost:{local_port}" diff --git a/backend/db/migrations/create_bridge_sync_tables.py b/backend/db/migrations/create_bridge_sync_tables.py index 68ef34a..6bb2b4a 100644 --- a/backend/db/migrations/create_bridge_sync_tables.py +++ b/backend/db/migrations/create_bridge_sync_tables.py @@ -19,7 +19,7 @@ def create_bridge_sync_tables(): try: database_url = os.getenv( "DATABASE_URL", - "postgresql://postgres:postgres@localhost:2345/btaa_geospatial_api_test", + "postgresql://postgres:postgres@localhost:2345/opengeometadata_api_test", ) sync_database_url = database_url.replace("postgresql+asyncpg://", "postgresql://") diff --git a/backend/db/migrations/create_data_dictionary_tables.py b/backend/db/migrations/create_data_dictionary_tables.py index bcf541a..5d2e8ba 100644 --- a/backend/db/migrations/create_data_dictionary_tables.py +++ b/backend/db/migrations/create_data_dictionary_tables.py @@ -19,7 +19,7 @@ def create_data_dictionary_tables(): try: database_url = os.getenv( "DATABASE_URL", - "postgresql://postgres:postgres@localhost:2345/btaa_ogm_api_test", + "postgresql://postgres:postgres@localhost:2345/opengeometadata_api_test", ) sync_database_url = database_url.replace("postgresql+asyncpg://", "postgresql://") diff --git a/backend/db/migrations/create_distribution_tables.py b/backend/db/migrations/create_distribution_tables.py index 1ca0698..9ef5c55 100644 --- a/backend/db/migrations/create_distribution_tables.py +++ b/backend/db/migrations/create_distribution_tables.py @@ -23,7 +23,7 @@ def create_distribution_tables(): """ try: # Get database URL from environment and ensure it's synchronous - database_url = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@localhost:2345/btaa_ogm_api_test") + database_url = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@localhost:2345/opengeometadata_api_test") sync_database_url = database_url.replace("postgresql+asyncpg://", "postgresql://") # Create engine diff --git a/backend/db/migrations/create_distribution_types_table.py b/backend/db/migrations/create_distribution_types_table.py index 769d291..eb6f385 100644 --- a/backend/db/migrations/create_distribution_types_table.py +++ b/backend/db/migrations/create_distribution_types_table.py @@ -22,7 +22,7 @@ def create_distribution_types_table(): """ try: # Get database URL from environment and ensure it's synchronous - database_url = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@localhost:2345/btaa_ogm_api_test") + database_url = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@localhost:2345/opengeometadata_api_test") sync_database_url = database_url.replace("postgresql+asyncpg://", "postgresql://") # Create engine diff --git a/backend/db/migrations/create_gazetteer_tables.py b/backend/db/migrations/create_gazetteer_tables.py index b6ff9bb..3dd55e7 100644 --- a/backend/db/migrations/create_gazetteer_tables.py +++ b/backend/db/migrations/create_gazetteer_tables.py @@ -19,7 +19,7 @@ def create_gazetteer_tables(): """Create the gazetteer tables.""" try: # Get database URL from environment and ensure it's synchronous - database_url = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@localhost:2345/btaa_ogm_api_test") + database_url = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@localhost:2345/opengeometadata_api_test") sync_database_url = database_url.replace("postgresql+asyncpg://", "postgresql://") # Create engine diff --git a/backend/db/migrations/create_generated_api_responses_table.py b/backend/db/migrations/create_generated_api_responses_table.py index 1410661..54f0aac 100644 --- a/backend/db/migrations/create_generated_api_responses_table.py +++ b/backend/db/migrations/create_generated_api_responses_table.py @@ -17,7 +17,7 @@ def create_generated_api_responses_table() -> None: """Create durable storage for generated public API response cache records.""" database_url = os.getenv( "DATABASE_URL", - "postgresql://postgres:postgres@localhost:2345/btaa_ogm_api_test", + "postgresql://postgres:postgres@localhost:2345/opengeometadata_api_test", ) sync_database_url = database_url.replace("postgresql+asyncpg://", "postgresql://") engine = create_engine(sync_database_url) diff --git a/backend/db/migrations/create_generated_resource_representations_table.py b/backend/db/migrations/create_generated_resource_representations_table.py index 9824dcb..dcb82b4 100644 --- a/backend/db/migrations/create_generated_resource_representations_table.py +++ b/backend/db/migrations/create_generated_resource_representations_table.py @@ -22,7 +22,7 @@ def create_generated_resource_representations_table() -> None: """Create durable storage for generated JSON:API resource and response caches.""" database_url = os.getenv( "DATABASE_URL", - "postgresql://postgres:postgres@localhost:2345/btaa_ogm_api_test", + "postgresql://postgres:postgres@localhost:2345/opengeometadata_api_test", ) sync_database_url = database_url.replace("postgresql+asyncpg://", "postgresql://") engine = create_engine(sync_database_url) diff --git a/backend/db/migrations/create_generated_visual_assets_table.py b/backend/db/migrations/create_generated_visual_assets_table.py index 7804062..d18632d 100644 --- a/backend/db/migrations/create_generated_visual_assets_table.py +++ b/backend/db/migrations/create_generated_visual_assets_table.py @@ -17,7 +17,7 @@ def create_generated_visual_assets_table() -> None: """Create durable storage for generated visual bytes and resource links.""" database_url = os.getenv( "DATABASE_URL", - "postgresql://postgres:postgres@localhost:2345/btaa_ogm_api_test", + "postgresql://postgres:postgres@localhost:2345/opengeometadata_api_test", ) sync_database_url = database_url.replace("postgresql+asyncpg://", "postgresql://") engine = create_engine(sync_database_url) diff --git a/backend/db/migrations/create_gin_blog_posts_table.py b/backend/db/migrations/create_gin_blog_posts_table.py index 6eabd68..f56a144 100644 --- a/backend/db/migrations/create_gin_blog_posts_table.py +++ b/backend/db/migrations/create_gin_blog_posts_table.py @@ -15,7 +15,7 @@ def create_gin_blog_posts_table() -> None: """Create gin_blog_posts table (idempotent).""" database_url = os.getenv( "DATABASE_URL", - "postgresql://postgres:postgres@localhost:2345/btaa_ogm_api_test", + "postgresql://postgres:postgres@localhost:2345/opengeometadata_api_test", ) sync_database_url = database_url.replace("postgresql+asyncpg://", "postgresql://") engine = create_engine(sync_database_url) diff --git a/backend/db/migrations/create_item_relationships.py b/backend/db/migrations/create_item_relationships.py index 3daa193..d02715b 100644 --- a/backend/db/migrations/create_item_relationships.py +++ b/backend/db/migrations/create_item_relationships.py @@ -19,7 +19,7 @@ def create_relationships_table(): """Create the resource_relationships table.""" try: # Get database URL from environment and ensure it's synchronous - database_url = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@localhost:2345/btaa_ogm_api_test") + database_url = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@localhost:2345/opengeometadata_api_test") sync_database_url = database_url.replace("postgresql+asyncpg://", "postgresql://") # Create engine diff --git a/backend/db/migrations/create_ogm_harvest_tables.py b/backend/db/migrations/create_ogm_harvest_tables.py index ee2223f..49f19d1 100644 --- a/backend/db/migrations/create_ogm_harvest_tables.py +++ b/backend/db/migrations/create_ogm_harvest_tables.py @@ -21,7 +21,7 @@ def create_ogm_harvest_tables(): # Get database URL from environment and ensure it's synchronous database_url = os.getenv( "DATABASE_URL", - "postgresql://postgres:postgres@localhost:2345/btaa_ogm_api_test", + "postgresql://postgres:postgres@localhost:2345/opengeometadata_api_test", ) sync_database_url = database_url.replace("postgresql+asyncpg://", "postgresql://") diff --git a/backend/db/migrations/create_resource_aux_tables.py b/backend/db/migrations/create_resource_aux_tables.py index bf2c140..1aa25a7 100644 --- a/backend/db/migrations/create_resource_aux_tables.py +++ b/backend/db/migrations/create_resource_aux_tables.py @@ -30,7 +30,7 @@ def create_resource_aux_tables() -> None: try: database_url = os.getenv( "DATABASE_URL", - "postgresql://postgres:postgres@localhost:2345/btaa_ogm_api_test", + "postgresql://postgres:postgres@localhost:2345/opengeometadata_api_test", ) sync_database_url = database_url.replace("postgresql+asyncpg://", "postgresql://") diff --git a/backend/db/migrations/create_resource_distributions_table.py b/backend/db/migrations/create_resource_distributions_table.py index 2892312..7733e85 100644 --- a/backend/db/migrations/create_resource_distributions_table.py +++ b/backend/db/migrations/create_resource_distributions_table.py @@ -22,7 +22,7 @@ def create_resource_distributions_table(): """ try: # Get database URL from environment and ensure it's synchronous - database_url = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@localhost:2345/btaa_ogm_api_test") + database_url = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@localhost:2345/opengeometadata_api_test") sync_database_url = database_url.replace("postgresql+asyncpg://", "postgresql://") # Create engine diff --git a/backend/db/migrations/create_resource_relationships.py b/backend/db/migrations/create_resource_relationships.py index 3daa193..d02715b 100644 --- a/backend/db/migrations/create_resource_relationships.py +++ b/backend/db/migrations/create_resource_relationships.py @@ -19,7 +19,7 @@ def create_relationships_table(): """Create the resource_relationships table.""" try: # Get database URL from environment and ensure it's synchronous - database_url = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@localhost:2345/btaa_ogm_api_test") + database_url = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@localhost:2345/opengeometadata_api_test") sync_database_url = database_url.replace("postgresql+asyncpg://", "postgresql://") # Create engine diff --git a/backend/db/migrations/create_resource_spatial_facets_table.py b/backend/db/migrations/create_resource_spatial_facets_table.py index b45c3e4..f21ec53 100644 --- a/backend/db/migrations/create_resource_spatial_facets_table.py +++ b/backend/db/migrations/create_resource_spatial_facets_table.py @@ -22,7 +22,7 @@ def create_resource_spatial_facets_table(): """ try: # Get database URL from environment and ensure it's synchronous - database_url = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@localhost:2345/btaa_ogm_api_test") + database_url = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@localhost:2345/opengeometadata_api_test") sync_database_url = database_url.replace("postgresql+asyncpg://", "postgresql://") # Create engine diff --git a/backend/db/migrations/create_resource_thumbnail_state_table.py b/backend/db/migrations/create_resource_thumbnail_state_table.py index b0743a4..e712869 100644 --- a/backend/db/migrations/create_resource_thumbnail_state_table.py +++ b/backend/db/migrations/create_resource_thumbnail_state_table.py @@ -17,7 +17,7 @@ def create_resource_thumbnail_state_table(): try: database_url = os.getenv( "DATABASE_URL", - "postgresql://postgres:postgres@localhost:2345/btaa_ogm_api_test", + "postgresql://postgres:postgres@localhost:2345/opengeometadata_api_test", ) sync_database_url = database_url.replace("postgresql+asyncpg://", "postgresql://") engine = create_engine(sync_database_url) diff --git a/backend/db/migrations/import_from_old_production.py b/backend/db/migrations/import_from_old_production.py index 8b801c7..a6e0107 100644 --- a/backend/db/migrations/import_from_old_production.py +++ b/backend/db/migrations/import_from_old_production.py @@ -53,7 +53,7 @@ def get_new_db_connection(): db_password = os.getenv("DB_PASSWORD", "postgres") db_host = os.getenv("DB_HOST", "localhost") db_port = os.getenv("DB_PORT", "2345") - db_name = os.getenv("DB_NAME", "btaa_geospatial_api") + db_name = os.getenv("DB_NAME", "opengeometadata_api") new_db_url = f"postgresql://{db_user}:{db_password}@{db_host}:{db_port}/{db_name}" logger.info(f"Connecting to new production database: {db_name}") diff --git a/backend/db/migrations/initialize_api_tiers.py b/backend/db/migrations/initialize_api_tiers.py index 931d396..3adf84b 100644 --- a/backend/db/migrations/initialize_api_tiers.py +++ b/backend/db/migrations/initialize_api_tiers.py @@ -1,8 +1,8 @@ import logging -import sys import os -from pathlib import Path +import sys from datetime import datetime +from pathlib import Path from urllib.parse import urlparse, urlunparse from dotenv import load_dotenv @@ -32,83 +32,128 @@ def initialize_api_tiers(): """Initialize the six service tiers with their rate limits.""" try: # Get database URL from environment and ensure it's synchronous - database_url = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@localhost:2345/btaa_ogm_api_test") - + database_url = os.getenv( + "DATABASE_URL", "postgresql://postgres:postgres@localhost:2345/opengeometadata_api_test" + ) + # Convert asyncpg URL to sync URL sync_database_url = database_url.replace("postgresql+asyncpg://", "postgresql://") - + # Handle Docker hostnames for local development - # If running locally (not in Docker) and DATABASE_URL points to a Docker service, convert to localhost:2345 + # Convert Docker service URLs to the locally published database port. parsed = urlparse(sync_database_url) is_docker = os.getenv("IS_DOCKER", "false").lower() == "true" - if not is_docker and parsed.hostname and ("paradedb" in parsed.hostname or "btaa-geospatial-api" in parsed.hostname): + if not is_docker and parsed.hostname and "paradedb" in parsed.hostname: # Replace Docker hostname with localhost and use port 2345 for local development # Use local database credentials from environment or defaults local_port = os.getenv("DB_PORT", "2345") local_user = os.getenv("DB_USER", "postgres") local_password = os.getenv("DB_PASSWORD", "postgres") - local_db = os.getenv("DB_NAME", parsed.path.lstrip("/") if parsed.path else "btaa_geospatial_api") - + local_db = os.getenv( + "DB_NAME", parsed.path.lstrip("/") if parsed.path else "opengeometadata_api" + ) + # Build new netloc with local credentials new_netloc = f"{local_user}:{local_password}@localhost:{local_port}" sync_database_url = urlunparse(parsed._replace(netloc=new_netloc, path=f"/{local_db}")) - logger.info(f"Converted Docker hostname to localhost:{local_port} with local credentials") - + logger.info( + f"Converted Docker hostname to localhost:{local_port} with local credentials" + ) + # Create engine engine = create_engine(sync_database_url) # Current timestamp now = datetime.utcnow() - # Define tiers + # The aliases are retained only to migrate existing tier rows in place; + # API-key foreign keys continue to point to the same row IDs. tiers = [ { - "tier_name": "btaa_primary", - "display_name": "BTAA Primary", + "tier_name": "ogm_primary", + "legacy_tier_name": "btaa_primary", + "display_name": "OGM Primary", "requests_per_minute": None, # Unlimited - "description": "BTAA Geoportal Frontend - highest priority, unlimited access", + "description": "OpenGeoMetadata API Frontend - highest priority, unlimited access", }, { - "tier_name": "btaa_secondary", - "display_name": "BTAA Secondary", + "tier_name": "ogm_secondary", + "legacy_tier_name": "btaa_secondary", + "display_name": "OGM Secondary", "requests_per_minute": None, # Unlimited - "description": "BTAA Secondary Applications - high priority, unlimited access", + "description": "OpenGeoMetadata applications - high priority, unlimited access", }, { - "tier_name": "btaa_member_primary", - "display_name": "BTAA Member Primary", + "tier_name": "ogm_member_primary", + "legacy_tier_name": "btaa_member_primary", + "display_name": "OGM Member Primary", "requests_per_minute": 1000, - "description": "Big Ten Member University Primary Keys - high priority, 1000 requests/minute", + "description": ( + "OpenGeoMetadata Member University Primary Keys - high priority, " + "1000 requests/minute" + ), }, { - "tier_name": "btaa_member_affiliated", - "display_name": "BTAA Member Affiliated", + "tier_name": "ogm_member_affiliated", + "legacy_tier_name": "btaa_member_affiliated", + "display_name": "OGM Member Affiliated", "requests_per_minute": 500, - "description": "BTAA Member Affiliated Applications - standard priority, 500 requests/minute", + "description": ( + "OpenGeoMetadata Member Affiliated Applications - standard priority, " + "500 requests/minute" + ), }, { "tier_name": "general_registered", + "legacy_tier_name": None, "display_name": "General Registered", "requests_per_minute": 100, "description": "General Registered Users - lower priority, 100 requests/minute", }, { "tier_name": "anonymous", + "legacy_tier_name": None, "display_name": "Anonymous", "requests_per_minute": 10, - "description": "No API Key - lowest priority, 10 requests/minute, encourages registration", + "description": ( + "No API Key - lowest priority, 10 requests/minute, encourages registration" + ), }, ] with engine.connect() as conn: for tier in tiers: - # Check if tier already exists - check_stmt = text( - "SELECT id FROM api_service_tiers WHERE tier_name = :tier_name" - ) + # Rename a legacy tier in place when the OGM tier is not present. + check_stmt = text("SELECT id FROM api_service_tiers WHERE tier_name = :tier_name") result = conn.execute(check_stmt, {"tier_name": tier["tier_name"]}) existing = result.first() + legacy_name = tier["legacy_tier_name"] + if not existing and legacy_name: + legacy_result = conn.execute(check_stmt, {"tier_name": legacy_name}) + legacy_existing = legacy_result.first() + if legacy_existing: + conn.execute( + text( + """ + UPDATE api_service_tiers + SET tier_name = :tier_name, + display_name = :display_name, + requests_per_minute = :requests_per_minute, + description = :description, + updated_at = :updated_at + WHERE id = :tier_id + """ + ), + { + **tier, + "tier_id": legacy_existing[0], + "updated_at": now, + }, + ) + logger.info("Renamed legacy API tier to '%s'", tier["tier_name"]) + continue + if existing: logger.info(f"Tier '{tier['tier_name']}' already exists, skipping") continue @@ -117,8 +162,10 @@ def initialize_api_tiers(): insert_stmt = text( """ INSERT INTO api_service_tiers - (tier_name, display_name, requests_per_minute, description, created_at, updated_at) - VALUES (:tier_name, :display_name, :requests_per_minute, :description, :created_at, :updated_at) + (tier_name, display_name, requests_per_minute, description, + created_at, updated_at) + VALUES (:tier_name, :display_name, :requests_per_minute, :description, + :created_at, :updated_at) """ ) conn.execute( @@ -144,5 +191,3 @@ def initialize_api_tiers(): if __name__ == "__main__": initialize_api_tiers() - - diff --git a/backend/db/migrations/migrate_document_distributions.py b/backend/db/migrations/migrate_document_distributions.py index bd59cc9..d954fbd 100644 --- a/backend/db/migrations/migrate_document_distributions.py +++ b/backend/db/migrations/migrate_document_distributions.py @@ -68,7 +68,7 @@ def get_new_engine() -> Engine: db_password = get_env("DB_PASSWORD", "postgres") db_host = get_env("DB_HOST", "localhost") db_port = get_env("DB_PORT", "2345") - db_name = get_env("DB_NAME", "btaa_geospatial_api") + db_name = get_env("DB_NAME", "opengeometadata_api") url = f"postgresql://{db_user}:{db_password}@{db_host}:{db_port}/{db_name}" logger.info("Connecting to new database: %s", db_name) diff --git a/backend/db/migrations/migrate_resource_data_dictionaries.py b/backend/db/migrations/migrate_resource_data_dictionaries.py index 9a398e9..310c704 100644 --- a/backend/db/migrations/migrate_resource_data_dictionaries.py +++ b/backend/db/migrations/migrate_resource_data_dictionaries.py @@ -46,7 +46,7 @@ def get_new_engine() -> Engine: db_password = get_env("DB_PASSWORD", "postgres") db_host = get_env("DB_HOST", "localhost") db_port = get_env("DB_PORT", "2345") - db_name = get_env("DB_NAME", "btaa_geospatial_api") + db_name = get_env("DB_NAME", "opengeometadata_api") url = f"postgresql://{db_user}:{db_password}@{db_host}:{db_port}/{db_name}" logger.info("Connecting to new database: %s", db_name) return create_engine(url) diff --git a/backend/db/migrations/optimize_spatial_facet_indexing.py b/backend/db/migrations/optimize_spatial_facet_indexing.py index 4e58af2..bac564b 100644 --- a/backend/db/migrations/optimize_spatial_facet_indexing.py +++ b/backend/db/migrations/optimize_spatial_facet_indexing.py @@ -21,7 +21,7 @@ def optimize_spatial_facet_indexing(): try: database_url = os.getenv( "DATABASE_URL", - "postgresql://postgres:postgres@localhost:2345/btaa_ogm_api_test", + "postgresql://postgres:postgres@localhost:2345/opengeometadata_api_test", ) sync_database_url = database_url.replace( "postgresql+asyncpg://", "postgresql://" diff --git a/backend/db/migrations/optimize_spatial_queries.py b/backend/db/migrations/optimize_spatial_queries.py index 0f8b9e1..19d5772 100644 --- a/backend/db/migrations/optimize_spatial_queries.py +++ b/backend/db/migrations/optimize_spatial_queries.py @@ -27,7 +27,7 @@ def optimize_spatial_queries(): """ try: # Get database URL from environment and ensure it's synchronous - database_url = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@localhost:2345/btaa_ogm_api_test") + database_url = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@localhost:2345/opengeometadata_api_test") sync_database_url = database_url.replace("postgresql+asyncpg://", "postgresql://") # Create engine diff --git a/backend/db/migrations/populate_resource_distributions.py b/backend/db/migrations/populate_resource_distributions.py index a5e925e..ccd3d94 100644 --- a/backend/db/migrations/populate_resource_distributions.py +++ b/backend/db/migrations/populate_resource_distributions.py @@ -23,7 +23,7 @@ def populate_resource_distributions(): """ try: # Get database URL from environment and ensure it's synchronous - database_url = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@localhost:2345/btaa_ogm_api_test") + database_url = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@localhost:2345/opengeometadata_api_test") sync_database_url = database_url.replace("postgresql+asyncpg://", "postgresql://") # Create engine diff --git a/backend/db/migrations/remove_geocoding_columns.py b/backend/db/migrations/remove_geocoding_columns.py index 669cd43..b21f318 100644 --- a/backend/db/migrations/remove_geocoding_columns.py +++ b/backend/db/migrations/remove_geocoding_columns.py @@ -33,7 +33,7 @@ def get_db_connection(): """Get database connection, handling Docker hostnames for local development.""" database_url = os.getenv( - "DATABASE_URL", "postgresql://postgres:postgres@localhost:2345/btaa_geospatial_api" + "DATABASE_URL", "postgresql://postgres:postgres@localhost:2345/opengeometadata_api" ) # Convert asyncpg URL to sync URL @@ -44,7 +44,7 @@ def get_db_connection(): # Check if hostname is a Docker service (needs conversion to localhost) is_docker_hostname = parsed.hostname and ( - "paradedb" in parsed.hostname or "btaa-geospatial-api" in parsed.hostname + "paradedb" in parsed.hostname ) # Determine connection parameters @@ -55,7 +55,7 @@ def get_db_connection(): local_user = os.getenv("DB_USER", parsed.username or "postgres") local_password = os.getenv("DB_PASSWORD", parsed.password or "postgres") local_db = os.getenv( - "DB_NAME", parsed.path.lstrip("/") if parsed.path else "btaa_geospatial_api" + "DB_NAME", parsed.path.lstrip("/") if parsed.path else "opengeometadata_api" ) local_host = "localhost" if is_docker_hostname else (parsed.hostname or "localhost") @@ -80,7 +80,7 @@ def get_db_connection(): return psycopg2.connect( host=parsed.hostname or "localhost", port=parsed.port or 5432, - database=parsed.path.lstrip("/") if parsed.path else "btaa_geospatial_api", + database=parsed.path.lstrip("/") if parsed.path else "opengeometadata_api", user=parsed.username or "postgres", password=parsed.password or "postgres", ) diff --git a/backend/db/migrations/rename_all_item_tables.py b/backend/db/migrations/rename_all_item_tables.py index 1ff8ed7..caa4545 100644 --- a/backend/db/migrations/rename_all_item_tables.py +++ b/backend/db/migrations/rename_all_item_tables.py @@ -17,7 +17,7 @@ def rename_all_item_tables(): """Rename all item_* tables to resource_* tables.""" try: # Get database URL from environment and ensure it's synchronous - database_url = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@paradedb:5432/btaa_ogm_api") + database_url = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@paradedb:5432/opengeometadata_api") sync_database_url = database_url.replace("postgresql+asyncpg://", "postgresql://") # Create engine diff --git a/backend/db/migrations/rename_api_usage_logs_to_analytics_api_usage_logs.py b/backend/db/migrations/rename_api_usage_logs_to_analytics_api_usage_logs.py index 606c326..2282fdc 100644 --- a/backend/db/migrations/rename_api_usage_logs_to_analytics_api_usage_logs.py +++ b/backend/db/migrations/rename_api_usage_logs_to_analytics_api_usage_logs.py @@ -108,20 +108,20 @@ def _set_table_id_sequence(conn, table_name: str) -> None: def _get_sync_database_url() -> str: database_url = os.getenv( "DATABASE_URL", - "postgresql://postgres:postgres@localhost:2345/btaa_geospatial_api", + "postgresql://postgres:postgres@localhost:2345/opengeometadata_api", ) sync_database_url = database_url.replace("postgresql+asyncpg://", "postgresql://") parsed = urlparse(sync_database_url) is_docker = os.getenv("IS_DOCKER", "false").lower() == "true" if not is_docker and parsed.hostname and ( - "paradedb" in parsed.hostname or "btaa-geospatial-api" in parsed.hostname + "paradedb" in parsed.hostname ): local_port = os.getenv("DB_PORT", "2345") local_user = os.getenv("DB_USER", "postgres") local_password = os.getenv("DB_PASSWORD", "postgres") local_db = os.getenv( - "DB_NAME", parsed.path.lstrip("/") if parsed.path else "btaa_geospatial_api" + "DB_NAME", parsed.path.lstrip("/") if parsed.path else "opengeometadata_api" ) new_netloc = f"{local_user}:{local_password}@localhost:{local_port}" sync_database_url = urlunparse(parsed._replace(netloc=new_netloc, path=f"/{local_db}")) diff --git a/backend/db/migrations/rename_friendlier_id_to_resource_id.py b/backend/db/migrations/rename_friendlier_id_to_resource_id.py index 4aa632b..ccdc301 100644 --- a/backend/db/migrations/rename_friendlier_id_to_resource_id.py +++ b/backend/db/migrations/rename_friendlier_id_to_resource_id.py @@ -24,7 +24,7 @@ def rename_friendlier_id_to_resource_id(): """ try: # Get database URL from environment and ensure it's synchronous - database_url = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@localhost:2345/btaa_ogm_api_test") + database_url = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@localhost:2345/opengeometadata_api_test") sync_database_url = database_url.replace("postgresql+asyncpg://", "postgresql://") # Create engine diff --git a/backend/db/migrations/rename_indexes.py b/backend/db/migrations/rename_indexes.py index ef4d7fa..319065a 100644 --- a/backend/db/migrations/rename_indexes.py +++ b/backend/db/migrations/rename_indexes.py @@ -17,7 +17,7 @@ def rename_indexes(): """Rename indexes that still reference old item_ naming.""" try: # Get database URL from environment and ensure it's synchronous - database_url = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@paradedb:5432/btaa_ogm_api") + database_url = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@paradedb:5432/opengeometadata_api") sync_database_url = database_url.replace("postgresql+asyncpg://", "postgresql://") # Create engine diff --git a/backend/db/migrations/rename_item_id_to_resource_id.py b/backend/db/migrations/rename_item_id_to_resource_id.py index 1d8ef02..eee0b4a 100644 --- a/backend/db/migrations/rename_item_id_to_resource_id.py +++ b/backend/db/migrations/rename_item_id_to_resource_id.py @@ -17,7 +17,7 @@ def rename_item_id_to_resource_id(): """Rename item_id columns to resource_id in relevant tables.""" try: # Get database URL from environment and ensure it's synchronous - database_url = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@paradedb:5432/btaa_ogm_api") + database_url = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@paradedb:5432/opengeometadata_api") sync_database_url = database_url.replace("postgresql+asyncpg://", "postgresql://") # Create engine diff --git a/backend/db/migrations/rename_items_to_resources.py b/backend/db/migrations/rename_items_to_resources.py index d613851..273b79e 100644 --- a/backend/db/migrations/rename_items_to_resources.py +++ b/backend/db/migrations/rename_items_to_resources.py @@ -17,7 +17,7 @@ def rename_items_to_resources(): """Rename the items table to resources.""" try: # Get database URL from environment and ensure it's synchronous - database_url = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@paradedb:5432/btaa_ogm_api") + database_url = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@paradedb:5432/opengeometadata_api") sync_database_url = database_url.replace("postgresql+asyncpg://", "postgresql://") # Create engine diff --git a/backend/db/migrations/rename_btaa_columns.py b/backend/db/migrations/rename_legacy_ogm_columns.py similarity index 90% rename from backend/db/migrations/rename_btaa_columns.py rename to backend/db/migrations/rename_legacy_ogm_columns.py index 53e178e..4c54e2f 100644 --- a/backend/db/migrations/rename_btaa_columns.py +++ b/backend/db/migrations/rename_legacy_ogm_columns.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Rename BTAA-specific columns to restore original casing.""" +"""Rename OGM-specific columns to restore original casing.""" import logging import os @@ -33,10 +33,10 @@ def _normalize_database_url(url: str) -> str: return url -def rename_btaa_columns(): - """Rename BTAA columns in the resources table back to camelCase.""" +def rename_legacy_ogm_columns(): + """Rename OGM columns in the resources table back to camelCase.""" database_url = os.getenv( - "DATABASE_URL", "postgresql://postgres:postgres@localhost:2345/btaa_ogm_api" + "DATABASE_URL", "postgresql://postgres:postgres@localhost:2345/opengeometadata_api" ) engine = create_engine(_normalize_database_url(database_url)) @@ -82,8 +82,8 @@ def rename_btaa_columns(): ) conn.commit() - logger.info("Completed BTAA column casing fixes.") + logger.info("Completed OGM column casing fixes.") if __name__ == "__main__": - rename_btaa_columns() + rename_legacy_ogm_columns() diff --git a/backend/db/migrations/rename_remaining_constraints.py b/backend/db/migrations/rename_remaining_constraints.py index 166b06c..f5837f3 100644 --- a/backend/db/migrations/rename_remaining_constraints.py +++ b/backend/db/migrations/rename_remaining_constraints.py @@ -17,7 +17,7 @@ def rename_remaining_constraints(): """Rename remaining constraints and indexes that still reference old naming.""" try: # Get database URL from environment and ensure it's synchronous - database_url = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@paradedb:5432/btaa_ogm_api") + database_url = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@paradedb:5432/opengeometadata_api") sync_database_url = database_url.replace("postgresql+asyncpg://", "postgresql://") # Create engine diff --git a/backend/db/migrations/rollback_spatial_optimizations.py b/backend/db/migrations/rollback_spatial_optimizations.py index b85163e..190cc72 100644 --- a/backend/db/migrations/rollback_spatial_optimizations.py +++ b/backend/db/migrations/rollback_spatial_optimizations.py @@ -26,7 +26,7 @@ def rollback_spatial_optimizations(): """ try: # Get database URL from environment and ensure it's synchronous - database_url = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@localhost:2345/btaa_ogm_api_test") + database_url = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@localhost:2345/opengeometadata_api_test") sync_database_url = database_url.replace("postgresql+asyncpg://", "postgresql://") # Create engine diff --git a/backend/db/migrations/sync_old_production_references.py b/backend/db/migrations/sync_old_production_references.py index 4c1d554..cd97173 100644 --- a/backend/db/migrations/sync_old_production_references.py +++ b/backend/db/migrations/sync_old_production_references.py @@ -3,7 +3,7 @@ """Sync old-production legacy references, downloads, and assets into the API DB. This migration fills the gaps left by the old `kithe_to_resources_bridge` materialized -view for curated BTAA-GIN datasets. In old production, critical download/PMTiles +view for curated legacy GIN datasets. In old production, critical download/PMTiles references often live on child Kithe asset rows rather than in the parent `json_attributes['dct_references_s']` blob or `document_distributions`. @@ -83,7 +83,7 @@ def get_new_engine() -> Engine: db_password = get_env("DB_PASSWORD", "postgres") db_host = get_env("DB_HOST", "localhost") db_port = get_env("DB_PORT", "2345") - db_name = get_env("DB_NAME", "btaa_geospatial_api") + db_name = get_env("DB_NAME", "opengeometadata_api") url = f"postgresql://{db_user}:{db_password}@{db_host}:{db_port}/{db_name}" logger.info("Connecting to new database: %s", db_name) return create_engine(url) diff --git a/backend/db/migrations/test_distribution_migrations.py b/backend/db/migrations/test_distribution_migrations.py index ef40d8e..70dd5be 100755 --- a/backend/db/migrations/test_distribution_migrations.py +++ b/backend/db/migrations/test_distribution_migrations.py @@ -24,7 +24,7 @@ def test_distribution_migrations(): """ try: # Get database URL from environment and ensure it's synchronous - database_url = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@localhost:2345/btaa_ogm_api_test") + database_url = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@localhost:2345/opengeometadata_api_test") sync_database_url = database_url.replace("postgresql+asyncpg://", "postgresql://") # Create engine diff --git a/backend/db/migrations/update_ogm_field_names.py b/backend/db/migrations/update_ogm_field_names.py index 1ae6b96..76633eb 100644 --- a/backend/db/migrations/update_ogm_field_names.py +++ b/backend/db/migrations/update_ogm_field_names.py @@ -24,7 +24,7 @@ def update_ogm_field_names(): # Get database URL from environment database_url = os.getenv( - "DATABASE_URL", "postgresql://postgres:postgres@localhost:2345/btaa_ogm_api" + "DATABASE_URL", "postgresql://postgres:postgres@localhost:2345/opengeometadata_api" ) # Convert async URL to sync URL @@ -46,7 +46,7 @@ def update_ogm_field_names(): "gbl_wxsidentifier_s": "gbl_wxsIdentifier_s", "gbl_displaynote_sm": "gbl_displayNote_sm", - # BTAA-specific fields + # OGM-specific fields "b1g_dct_accrualmethod_s": "b1g_dct_accrualMethod_s", "b1g_dct_accrualperiodicity_s": "b1g_dct_accrualPeriodicity_s", "b1g_dateaccessioned_s": "b1g_dateAccessioned_s", @@ -122,7 +122,7 @@ def verify_schema_update(): # Get database URL from environment database_url = os.getenv( - "DATABASE_URL", "postgresql://postgres:postgres@localhost:2345/btaa_ogm_api" + "DATABASE_URL", "postgresql://postgres:postgres@localhost:2345/opengeometadata_api" ) # Convert async URL to sync URL diff --git a/backend/db/migrations/update_spatial_facets_for_wof_ids.py b/backend/db/migrations/update_spatial_facets_for_wof_ids.py index 454a4fe..48d3b9b 100644 --- a/backend/db/migrations/update_spatial_facets_for_wof_ids.py +++ b/backend/db/migrations/update_spatial_facets_for_wof_ids.py @@ -25,7 +25,7 @@ def update_spatial_facets_for_wof_ids(): """ try: # Get database URL from environment and ensure it's synchronous - database_url = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@localhost:2345/btaa_ogm_api_test") + database_url = os.getenv("DATABASE_URL", "postgresql://postgres:postgres@localhost:2345/opengeometadata_api_test") sync_database_url = database_url.replace("postgresql+asyncpg://", "postgresql://") # Create engine diff --git a/backend/db/models.py b/backend/db/models.py index 5ea8c64..7438e6c 100644 --- a/backend/db/models.py +++ b/backend/db/models.py @@ -65,7 +65,7 @@ Column("gbl_mdVersion_s", String), Column("gbl_suppressed_b", Boolean), Column("gbl_georeferenced_b", Boolean), - # BTAA-specific fields for OGM Aardvark compliance + # OGM-specific fields for OGM Aardvark compliance Column("b1g_code_s", String), Column("b1g_status_s", String), Column("b1g_dct_accrualMethod_s", String), @@ -85,7 +85,7 @@ Column("b1g_geodcat_spatialResolutionAsText_sm", ARRAY(String)), Column("b1g_dct_provenanceStatement_sm", ARRAY(String)), Column("b1g_adminTags_sm", ARRAY(String)), - # Latest BTAA schema compatibility fields + # Latest OGM schema compatibility fields Column("b1g_adminNote_sm", ARRAY(String)), Column("b1g_dateAccessioned_dt", TIMESTAMP), Column("b1g_dateRetired_dt", TIMESTAMP), @@ -96,7 +96,7 @@ Column("b1g_dct_provenance_sm", ARRAY(String)), Column("b1g_dcat_spatialResolutionInMeters_s", String), Column("b1g_websitePlatform_s", String), - # Additional BTAA fields for old database migration + # Additional OGM fields for old database migration Column("b1g_adms_supportedSchema_sm", ARRAY(String)), Column("b1g_dateAccessioned_sm", ARRAY(String)), # Note: array version for migration Column("b1g_dcat_endpointDescription_s", String), @@ -238,8 +238,10 @@ Column("updated_at", TIMESTAMP), ) -# BTAA gazetteer -gazetteer_btaa = Table( +# OGM gazetteer +gazetteer_ogm = Table( + # Keep the populated production table name until a two-release migration + # can rename it without creating a rolling-deploy compatibility gap. "gazetteer_btaa", metadata, Column("id", Integer, primary_key=True), diff --git a/backend/examples/advanced_queries_example.py b/backend/examples/advanced_queries_example.py index c2453fa..bf8e261 100644 --- a/backend/examples/advanced_queries_example.py +++ b/backend/examples/advanced_queries_example.py @@ -2,7 +2,7 @@ import json from IPython.display import HTML, display -url = 'https://lib-btaageoapi-dev-app-01.oit.umn.edu/api/v1/search' +url = 'https://ogm.geo4lib.app/api/v1/search' headers = {'accept': 'application/json'} # ============================================================================ diff --git a/backend/examples/facet_search_example.py b/backend/examples/facet_search_example.py index 10414a5..02235a7 100644 --- a/backend/examples/facet_search_example.py +++ b/backend/examples/facet_search_example.py @@ -69,7 +69,7 @@ def display_table(headers, rows): # Configure base URL - change this to your local server if running locally # Local: 'http://localhost:8000/api/v1' -# Dev: 'https://lib-btaageoapi-dev-app-01.oit.umn.edu/api/v1' +# Dev: 'https://ogm.geo4lib.app/api/v1' base_url = 'http://localhost:8000/api/v1' # Change this to match your setup headers = {'accept': 'application/json'} diff --git a/backend/examples/geo_polygon_search_example.py b/backend/examples/geo_polygon_search_example.py index b629c31..2e564ec 100644 --- a/backend/examples/geo_polygon_search_example.py +++ b/backend/examples/geo_polygon_search_example.py @@ -2,7 +2,7 @@ import json from IPython.display import HTML, display -url = 'https://lib-btaageoapi-dev-app-01.oit.umn.edu/api/v1/search' +url = 'https://ogm.geo4lib.app/api/v1/search' headers = {'accept': 'application/json'} # ============================================================================ diff --git a/backend/examples/spatial_search_bbox_example.py b/backend/examples/spatial_search_bbox_example.py index 2919791..05c3ede 100644 --- a/backend/examples/spatial_search_bbox_example.py +++ b/backend/examples/spatial_search_bbox_example.py @@ -2,7 +2,7 @@ import json from IPython.display import HTML, display -url = 'https://lib-btaageoapi-dev-app-01.oit.umn.edu/api/v1/search' +url = 'https://ogm.geo4lib.app/api/v1/search' headers = {'accept': 'application/json'} # ============================================================================ diff --git a/backend/examples/spatial_search_distance_example.py b/backend/examples/spatial_search_distance_example.py index 2a5061a..2d4c3de 100644 --- a/backend/examples/spatial_search_distance_example.py +++ b/backend/examples/spatial_search_distance_example.py @@ -2,7 +2,7 @@ import json from IPython.display import HTML, display -url = 'https://lib-btaageoapi-dev-app-01.oit.umn.edu/api/v1/search' +url = 'https://ogm.geo4lib.app/api/v1/search' headers = {'accept': 'application/json'} # ============================================================================ diff --git a/backend/examples/spatial_search_polygon_relation_example.py b/backend/examples/spatial_search_polygon_relation_example.py index 90757c8..b21ea6d 100644 --- a/backend/examples/spatial_search_polygon_relation_example.py +++ b/backend/examples/spatial_search_polygon_relation_example.py @@ -2,7 +2,7 @@ import json from IPython.display import HTML, display -url = 'https://lib-btaageoapi-dev-app-01.oit.umn.edu/api/v1/search' +url = 'https://ogm.geo4lib.app/api/v1/search' headers = {'accept': 'application/json'} # ============================================================================ diff --git a/backend/examples/spatial_search_shape_envelope_example.py b/backend/examples/spatial_search_shape_envelope_example.py index 0a6ef36..77d2e23 100644 --- a/backend/examples/spatial_search_shape_envelope_example.py +++ b/backend/examples/spatial_search_shape_envelope_example.py @@ -2,7 +2,7 @@ import json from IPython.display import HTML, display -url = 'https://lib-btaageoapi-dev-app-01.oit.umn.edu/api/v1/search' +url = 'https://ogm.geo4lib.app/api/v1/search' headers = {'accept': 'application/json'} # ============================================================================ diff --git a/backend/scripts/backup_elasticsearch.py b/backend/scripts/backup_elasticsearch.py index c103208..3ee1fab 100755 --- a/backend/scripts/backup_elasticsearch.py +++ b/backend/scripts/backup_elasticsearch.py @@ -33,7 +33,7 @@ # Use ELASTICSEARCH_URL from environment or default ELASTICSEARCH_URL = os.getenv("ELASTICSEARCH_URL", "http://localhost:9200") -INDEX_NAME = os.getenv("ELASTICSEARCH_INDEX", "btaa_geospatial_api") +INDEX_NAME = os.getenv("ELASTICSEARCH_INDEX", "opengeometadata_api") # Snapshot repository configuration. The historical default is a filesystem # repository; production can switch to an S3 repository through environment. @@ -41,7 +41,7 @@ REPOSITORY_TYPE = os.getenv("ELASTICSEARCH_SNAPSHOT_REPOSITORY_TYPE", "fs").strip().lower() REPOSITORY_PATH = os.getenv("ELASTICSEARCH_SNAPSHOT_PATH", "/usr/share/elasticsearch/backups") BACKUP_REQUIRED_DEST = os.getenv("BACKUP_REQUIRED_DEST", "prd").strip() -BACKUP_S3_PREFIX = os.getenv("BACKUP_S3_PREFIX", "btaa-geospatial-api").strip("/") +BACKUP_S3_PREFIX = os.getenv("BACKUP_S3_PREFIX", "opengeometadata-api").strip("/") # Add project root to path sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) diff --git a/backend/scripts/backup_postgres_to_s3.py b/backend/scripts/backup_postgres_to_s3.py index a460bcd..047e4ae 100644 --- a/backend/scripts/backup_postgres_to_s3.py +++ b/backend/scripts/backup_postgres_to_s3.py @@ -21,8 +21,8 @@ from tempfile import TemporaryDirectory from urllib.parse import unquote, urlsplit, urlunsplit -DEFAULT_DATABASE_NAME = "btaa_geospatial_api" -DEFAULT_PREFIX = "btaa-geospatial-api" +DEFAULT_DATABASE_NAME = "opengeometadata_api" +DEFAULT_PREFIX = "opengeometadata-api" DEFAULT_REQUIRED_DEST = "prd" DEFAULT_RETENTION_COUNT = 3 @@ -173,7 +173,7 @@ def _build_config() -> BackupConfig: prefix=prefix, retention_count=retention_count, database_url=_normalize_database_url(database_url), - work_dir=Path(os.getenv("BACKUP_WORK_DIR", "/tmp/btaa-geospatial-api-backups")), + work_dir=Path(os.getenv("BACKUP_WORK_DIR", "/tmp/opengeometadata-api-backups")), sse=os.getenv("BACKUP_S3_SSE") or None, sse_kms_key_id=os.getenv("BACKUP_S3_SSE_KMS_KEY_ID") or None, storage_class=os.getenv("BACKUP_S3_STORAGE_CLASS") or None, diff --git a/backend/scripts/bootstrap_kamal_deploy_user.sh b/backend/scripts/bootstrap_kamal_deploy_user.sh index 1c4ba90..c20f036 100755 --- a/backend/scripts/bootstrap_kamal_deploy_user.sh +++ b/backend/scripts/bootstrap_kamal_deploy_user.sh @@ -11,7 +11,7 @@ Bootstraps a shared Kamal deploy account on a remote server by: - creating the deploy group and user (default: deploy) - adding that user to the docker group - seeding /home//.ssh/authorized_keys from the current remote SSH user - - preparing /var/lib/btaa-geospatial-api for shared bind mounts + - preparing /var/lib/opengeometadata-api for shared bind mounts - creating an Elasticsearch bind-mount directory with group-write access for GID 0 Options: @@ -19,7 +19,7 @@ Options: --ssh-user USER Existing remote SSH user with passwordless sudo --ssh-port PORT SSH port (default: 22) --deploy-user USER Shared deploy user to create (default: deploy) - --shared-dir PATH Shared data directory (default: /var/lib/btaa-geospatial-api) + --shared-dir PATH Shared data directory (default: /var/lib/opengeometadata-api) --seed-remote-authorized-keys PATH Remote authorized_keys path to copy from (default: .ssh/authorized_keys) -h, --help Show this help @@ -30,7 +30,7 @@ host="" ssh_user="" ssh_port="22" deploy_user="deploy" -shared_dir="/var/lib/btaa-geospatial-api" +shared_dir="/var/lib/opengeometadata-api" seed_remote_authorized_keys=".ssh/authorized_keys" while (($# > 0)); do diff --git a/backend/scripts/check_elasticsearch_health.py b/backend/scripts/check_elasticsearch_health.py index 12a52ca..47239ca 100755 --- a/backend/scripts/check_elasticsearch_health.py +++ b/backend/scripts/check_elasticsearch_health.py @@ -28,7 +28,7 @@ # Use ELASTICSEARCH_URL from environment or default ELASTICSEARCH_URL = os.getenv("ELASTICSEARCH_URL", "http://localhost:9200") -INDEX_NAME = os.getenv("ELASTICSEARCH_INDEX", "btaa_geospatial_api") +INDEX_NAME = os.getenv("ELASTICSEARCH_INDEX", "opengeometadata_api") # Add project root to path for database imports sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) diff --git a/backend/scripts/check_mapping.py b/backend/scripts/check_mapping.py index ad9d9b3..654b29f 100644 --- a/backend/scripts/check_mapping.py +++ b/backend/scripts/check_mapping.py @@ -16,8 +16,8 @@ async def main(): - mapping = await es.indices.get_mapping(index="btaa_geospatial_api") - props = mapping["btaa_geospatial_api"]["mappings"]["properties"] + mapping = await es.indices.get_mapping(index="opengeometadata_api") + props = mapping["opengeometadata_api"]["mappings"]["properties"] print("geo_country:") print(json.dumps(props.get("geo_country", {}), indent=2)) diff --git a/backend/scripts/clone_db_for_tests.sh b/backend/scripts/clone_db_for_tests.sh index 587baa9..db27a99 100644 --- a/backend/scripts/clone_db_for_tests.sh +++ b/backend/scripts/clone_db_for_tests.sh @@ -1,18 +1,18 @@ #!/bin/bash -# Clone btaa_ogm_api database to btaa_ogm_api_test for testing +# Clone opengeometadata_api database to opengeometadata_api_test for testing set -e -echo "Cloning btaa_ogm_api to btaa_ogm_api_test..." +echo "Cloning opengeometadata_api to opengeometadata_api_test..." # Drop test DB if it exists -docker compose exec -T paradedb bash -lc 'PGPASSWORD=$POSTGRES_PASSWORD psql -U postgres -c "DROP DATABASE IF EXISTS btaa_ogm_api_test;"' +docker compose exec -T paradedb bash -lc 'PGPASSWORD=$POSTGRES_PASSWORD psql -U postgres -c "DROP DATABASE IF EXISTS opengeometadata_api_test;"' # Create test DB as a clone -docker compose exec -T paradedb bash -lc 'PGPASSWORD=$POSTGRES_PASSWORD psql -U postgres -c "CREATE DATABASE btaa_ogm_api_test WITH TEMPLATE btaa_ogm_api OWNER postgres;"' +docker compose exec -T paradedb bash -lc 'PGPASSWORD=$POSTGRES_PASSWORD psql -U postgres -c "CREATE DATABASE opengeometadata_api_test WITH TEMPLATE opengeometadata_api OWNER postgres;"' echo "✓ Database cloned successfully!" echo "" echo "To verify:" -echo " docker compose exec -T paradedb psql -U postgres -d btaa_ogm_api_test -c 'SELECT COUNT(*) FROM resources;'" +echo " docker compose exec -T paradedb psql -U postgres -d opengeometadata_api_test -c 'SELECT COUNT(*) FROM resources;'" diff --git a/backend/scripts/debug_county_query.py b/backend/scripts/debug_county_query.py index e2e226a..cbfccbe 100644 --- a/backend/scripts/debug_county_query.py +++ b/backend/scripts/debug_county_query.py @@ -19,7 +19,7 @@ async def debug_county_query(): """Debug the county query to see why we're getting so few results.""" # Database connection - DATABASE_URL = "postgresql+asyncpg://postgres:postgres@localhost:2345/btaa_ogm_api" + DATABASE_URL = "postgresql+asyncpg://postgres:postgres@localhost:2345/opengeometadata_api" engine = create_async_engine(DATABASE_URL) async_session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) diff --git a/backend/scripts/debug_index_missing_resources.py b/backend/scripts/debug_index_missing_resources.py index 0210254..baa91df 100644 --- a/backend/scripts/debug_index_missing_resources.py +++ b/backend/scripts/debug_index_missing_resources.py @@ -150,7 +150,7 @@ async def _index_one(index_name: str, resource_id: str) -> Tuple[bool, Optional[ async def main(argv: List[str]) -> int: args = _parse_args(argv) - index_name = args.index_name or "btaa_geospatial_api" + index_name = args.index_name or "opengeometadata_api" await database.connect() try: diff --git a/backend/scripts/debug_static_map.py b/backend/scripts/debug_static_map.py index 31f7815..450b9ac 100755 --- a/backend/scripts/debug_static_map.py +++ b/backend/scripts/debug_static_map.py @@ -34,7 +34,7 @@ def test_tile_server_connectivity(): try: req = urllib.request.Request(test_url) - req.add_header("User-Agent", "BTAA-Geospatial-API/1.0") + req.add_header("User-Agent", "OGM-Geospatial-API/1.0") with urllib.request.urlopen(req, timeout=10) as response: status = response.getcode() content_length = len(response.read()) diff --git a/backend/scripts/diagnose_index_failures.py b/backend/scripts/diagnose_index_failures.py index 3615561..430aebf 100644 --- a/backend/scripts/diagnose_index_failures.py +++ b/backend/scripts/diagnose_index_failures.py @@ -33,7 +33,7 @@ async def main(): - index_name = os.getenv("ELASTICSEARCH_INDEX", "btaa_geospatial_api") + index_name = os.getenv("ELASTICSEARCH_INDEX", "opengeometadata_api") es = AsyncElasticsearch(os.getenv("ELASTICSEARCH_URL", "http://elasticsearch:9200")) await database.connect() diff --git a/backend/scripts/diagnose_missing_resources.py b/backend/scripts/diagnose_missing_resources.py index 1fed1a9..b1c5c69 100644 --- a/backend/scripts/diagnose_missing_resources.py +++ b/backend/scripts/diagnose_missing_resources.py @@ -64,7 +64,7 @@ async def main(): db_count = db_result[0] # Get count from Elasticsearch - index_name = os.getenv("ELASTICSEARCH_INDEX", "btaa_geospatial_api") + index_name = os.getenv("ELASTICSEARCH_INDEX", "opengeometadata_api") es_url = os.getenv("ELASTICSEARCH_URL", "http://localhost:9200") es_count_result = await es.count(index=index_name) es_count = es_count_result.get("count", 0) diff --git a/backend/scripts/ingest_btaa_fixtures.py b/backend/scripts/ingest_ogm_fixtures.py similarity index 89% rename from backend/scripts/ingest_btaa_fixtures.py rename to backend/scripts/ingest_ogm_fixtures.py index 0629fa8..71013a5 100644 --- a/backend/scripts/ingest_btaa_fixtures.py +++ b/backend/scripts/ingest_ogm_fixtures.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 """ -Ingest BTAA fixture JSON files into the database. +Ingest OGM fixture JSON files into the database. -Reads all JSON files from data/fixtures/btaa_fixtures_data/ and imports them +Reads all JSON files from data/fixtures/ogm_fixtures_data/ and imports them using the OGMResourceImporter. """ @@ -40,13 +40,13 @@ async def load_json_file(file_path: Path) -> Optional[Dict[str, Any]]: return None -async def ingest_btaa_fixtures(fixtures_dir: Path, repo_name: str = "btaa_fixtures"): +async def ingest_ogm_fixtures(fixtures_dir: Path, repo_name: str = "ogm_fixtures"): """ Ingest all JSON fixture files from the specified directory. Args: fixtures_dir: Path to directory containing JSON fixture files - repo_name: Repository name to use for tagging (default: "btaa_fixtures") + repo_name: Repository name to use for tagging (default: "ogm_fixtures") """ if not fixtures_dir.exists(): logger.error(f"Fixtures directory not found: {fixtures_dir}") @@ -104,10 +104,10 @@ async def main(): await database.connect() logger.info("Database connection established") - # Fixtures dir: default btaa_fixtures_data, or first CLI arg (e.g. btaa_featured_resources). + # Fixtures dir: default ogm_fixtures_data, or first CLI arg (e.g. ogm_featured_resources). script_dir = Path(__file__).parent project_root = script_dir.parent - fixtures_subdir = sys.argv[1] if len(sys.argv) > 1 else "btaa_fixtures_data" + fixtures_subdir = sys.argv[1] if len(sys.argv) > 1 else "ogm_fixtures_data" repo_name = ( sys.argv[2] if len(sys.argv) > 2 @@ -118,7 +118,7 @@ async def main(): logger.info(f"Fixtures directory: {fixtures_dir} (repo_name={repo_name})") # Ingest fixtures - await ingest_btaa_fixtures(fixtures_dir, repo_name=repo_name) + await ingest_ogm_fixtures(fixtures_dir, repo_name=repo_name) except Exception as e: logger.error(f"Error in main: {e}", exc_info=True) diff --git a/backend/scripts/load_fixtures.py b/backend/scripts/load_fixtures.py index 2325c20..3096806 100755 --- a/backend/scripts/load_fixtures.py +++ b/backend/scripts/load_fixtures.py @@ -157,7 +157,7 @@ async def main(): try: # Get database URL from environment database_url = os.getenv( - "DATABASE_URL", "postgresql://postgres:postgres@localhost:2345/btaa_ogm_api_test" + "DATABASE_URL", "postgresql://postgres:postgres@localhost:2345/opengeometadata_api_test" ) sync_database_url = database_url.replace("postgresql+asyncpg://", "postgresql://") diff --git a/backend/scripts/monitor_reindex.py b/backend/scripts/monitor_reindex.py index 0999351..853e56b 100755 --- a/backend/scripts/monitor_reindex.py +++ b/backend/scripts/monitor_reindex.py @@ -33,7 +33,7 @@ load_dotenv() ELASTICSEARCH_URL = os.getenv("ELASTICSEARCH_URL", "http://localhost:9200") -INDEX_NAME = os.getenv("ELASTICSEARCH_INDEX", "btaa_geospatial_api") +INDEX_NAME = os.getenv("ELASTICSEARCH_INDEX", "opengeometadata_api") FAILURE_LOG = os.getenv("FAILURE_LOG", "logs/reindex_failures.log") PUBLISHED_ONLY = os.getenv("PUBLISHED_ONLY", "1").strip().lower() in {"1", "true", "t", "yes", "y"} USE_B1G_PUB_STATE = os.getenv("USE_B1G_PUBLICATION_STATE", "0").strip().lower() in { diff --git a/backend/scripts/ogm_importer.py b/backend/scripts/ogm_importer.py index 0ec091e..9b01483 100644 --- a/backend/scripts/ogm_importer.py +++ b/backend/scripts/ogm_importer.py @@ -116,7 +116,7 @@ def __init__( """ self.ogm_path = ogm_path or os.path.join("data", "opengeometadata") self.database_url = database_url or os.getenv( - "DATABASE_URL", "postgresql://postgres:postgres@localhost:2345/btaa_ogm_api" + "DATABASE_URL", "postgresql://postgres:postgres@localhost:2345/opengeometadata_api" ) self.batch_size = batch_size self.dry_run = dry_run @@ -269,7 +269,7 @@ def _clean_record_for_database(self, record: Dict[str, Any]) -> Dict[str, Any]: else: cleaned[key] = str(value) if value else None - # Handle JSON fields for BTAA-specific data + # Handle JSON fields for OGM-specific data elif key == "b1g_access_s": if isinstance(value, str): try: diff --git a/backend/scripts/populate_distributions.py b/backend/scripts/populate_distributions.py index 37a63cd..a55b41c 100755 --- a/backend/scripts/populate_distributions.py +++ b/backend/scripts/populate_distributions.py @@ -45,7 +45,7 @@ def populate_resource_distributions(): try: # Get database URL from environment and ensure it's synchronous database_url = os.getenv( - "DATABASE_URL", "postgresql://postgres:postgres@localhost:2345/btaa_ogm_api" + "DATABASE_URL", "postgresql://postgres:postgres@localhost:2345/opengeometadata_api" ) sync_database_url = database_url.replace("postgresql+asyncpg://", "postgresql://") diff --git a/backend/scripts/populate_ogm_repos.py b/backend/scripts/populate_ogm_repos.py index 0c312c6..135d42d 100644 --- a/backend/scripts/populate_ogm_repos.py +++ b/backend/scripts/populate_ogm_repos.py @@ -46,8 +46,8 @@ def _sync_database_url(database_url: str) -> str: parsed = urlparse(sync_url) docker_hostnames = { "paradedb", - "btaa-geospatial-api-paradedb", - "btaa-geospatial-api-paradedb-1", + "opengeometadata-api-paradedb", + "opengeometadata-api-paradedb-1", } if parsed.hostname in docker_hostnames: new_netloc = f"{parsed.username}:{parsed.password}@localhost:2345" diff --git a/backend/scripts/prime_thumbnail_cache.py b/backend/scripts/prime_thumbnail_cache.py index a15a6f1..6e8adb1 100644 --- a/backend/scripts/prime_thumbnail_cache.py +++ b/backend/scripts/prime_thumbnail_cache.py @@ -86,7 +86,7 @@ logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s") logger = logging.getLogger(__name__) -USER_AGENT = "BTAA-Geospatial-Data-API/1.0 (https://geo.btaa.org/)" +USER_AGENT = "OpenGeoMetadata-API/1.0 (https://opengeometadata.org/)" FALLBACK_ICON_DETAIL = "OGM resource-class fallback icon materialized" REMOTE_THUMBNAIL_MAX_BYTES = int(os.getenv("REMOTE_THUMBNAIL_MAX_BYTES", str(20 * 1024 * 1024))) diff --git a/backend/scripts/reindex.py b/backend/scripts/reindex.py index 8a7e1c9..b496fd6 100644 --- a/backend/scripts/reindex.py +++ b/backend/scripts/reindex.py @@ -7,7 +7,7 @@ - Exposes behavior via environment variables (see below) Environment: - - ELASTICSEARCH_INDEX: target index name (default: btaa_geospatial_api) + - ELASTICSEARCH_INDEX: target index name (default: opengeometadata_api) - PUBLISHED_ONLY: 1/true to index only published rows (default: 1) - USE_B1G_PUBLICATION_STATE: 1/true to use b1g_publication_state_s (default: 0) - BATCH_SIZE: DB fetch size (default: 2000) @@ -204,7 +204,7 @@ async def _verify_missing( async def main(): - index_name = os.getenv("ELASTICSEARCH_INDEX", "btaa_geospatial_api") + index_name = os.getenv("ELASTICSEARCH_INDEX", "opengeometadata_api") published_only = _env_bool("PUBLISHED_ONLY", True) use_b1g_pub_state = _env_bool("USE_B1G_PUBLICATION_STATE", False) batch_size = int(os.getenv("BATCH_SIZE", "2000")) diff --git a/backend/scripts/reindex_atomic.py b/backend/scripts/reindex_atomic.py index 5b5a6f8..fa6b39c 100644 --- a/backend/scripts/reindex_atomic.py +++ b/backend/scripts/reindex_atomic.py @@ -580,7 +580,7 @@ async def _prune_old_versioned_indices( async def main() -> None: - base_alias = os.getenv("ELASTICSEARCH_INDEX", "btaa_geospatial_api") + base_alias = os.getenv("ELASTICSEARCH_INDEX", "opengeometadata_api") published_only = _env_bool("PUBLISHED_ONLY", True) use_b1g_pub_state = _env_bool("USE_B1G_PUBLICATION_STATE", False) chunk_size = _env_int("REINDEX_ATOMIC_CHUNK_SIZE", 2000) diff --git a/backend/scripts/run_btaa_migration.py b/backend/scripts/run_btaa_migration.py deleted file mode 100644 index ea431dc..0000000 --- a/backend/scripts/run_btaa_migration.py +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env python3 -""" -Script to run the BTAA OGM Aardvark migration. - -This script adds the BTAA-specific fields to the resources table -to support BTAA flavored OGM Aardvark records. -""" - -import sys -from pathlib import Path - -# Add the project root directory to Python path -sys.path.append(str(Path(__file__).parent)) - -from db.migrations.add_btaa_ogm_fields import add_btaa_ogm_fields - - -def main(): - """Run the BTAA migration.""" - print("Starting BTAA OGM Aardvark migration...") - - try: - add_btaa_ogm_fields() - print("✅ BTAA migration completed successfully!") - except Exception as e: - print(f"❌ BTAA migration failed: {e}") - sys.exit(1) - - -if __name__ == "__main__": - main() diff --git a/backend/scripts/run_gazetteers.py b/backend/scripts/run_gazetteers.py index 1c6c4c7..80e2db5 100644 --- a/backend/scripts/run_gazetteers.py +++ b/backend/scripts/run_gazetteers.py @@ -3,7 +3,7 @@ Script to download and import gazetteer data. This script: -1. Downloads data from all supported gazetteers (GeoNames, Who's on First, BTAA, FAST) +1. Downloads data from all supported gazetteers (GeoNames, Who's on First, OGM, FAST) 2. Imports the downloaded data into the database 3. Provides detailed logging of the process @@ -74,8 +74,8 @@ def check_gazetteer_data_exists(gazetteer: str) -> bool: elif gazetteer == "fast": # Check for MARCXML file return (data_dir / "fast" / "FASTGeographic.marcxml").exists() - elif gazetteer == "btaa": - # BTAA data is not downloaded, it's created from other sources + elif gazetteer == "ogm": + # OGM data is not downloaded, it's created from other sources return True return False @@ -90,7 +90,7 @@ async def run_gazetteers(): # Step 1: Download gazetteer data logger.info("Step 1: Downloading gazetteer data...") download_results = {} - for gazetteer in ["geonames", "wof", "btaa", "fast"]: + for gazetteer in ["geonames", "wof", "ogm", "fast"]: if check_gazetteer_data_exists(gazetteer): logger.info(f"Data already exists for {gazetteer}, skipping download") download_results[gazetteer] = { diff --git a/backend/scripts/run_index.py b/backend/scripts/run_index.py index 32ca60c..523e40c 100644 --- a/backend/scripts/run_index.py +++ b/backend/scripts/run_index.py @@ -18,7 +18,7 @@ async def verify_index(): """Verify that the index exists and has documents.""" - index_name = os.getenv("ELASTICSEARCH_INDEX", "btaa_geospatial_api") + index_name = os.getenv("ELASTICSEARCH_INDEX", "opengeometadata_api") try: # Check if index exists diff --git a/backend/scripts/run_ogm_migration.py b/backend/scripts/run_ogm_migration.py new file mode 100644 index 0000000..f430c24 --- /dev/null +++ b/backend/scripts/run_ogm_migration.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +""" +Script to run the OGM Aardvark migration. + +This script adds the OGM-specific fields to the resources table +to support OGM flavored OGM Aardvark records. +""" + +import sys +from pathlib import Path + +# Add the project root directory to Python path +sys.path.append(str(Path(__file__).parent)) + +from db.migrations.add_ogm_fields import add_ogm_fields + + +def main(): + """Run the OGM migration.""" + print("Starting OGM Aardvark migration...") + + try: + add_ogm_fields() + print("✅ OGM migration completed successfully!") + except Exception as e: + print(f"❌ OGM migration failed: {e}") + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/backend/scripts/run_tests_with_real_data.sh b/backend/scripts/run_tests_with_real_data.sh index 5e06a33..f401a33 100644 --- a/backend/scripts/run_tests_with_real_data.sh +++ b/backend/scripts/run_tests_with_real_data.sh @@ -9,11 +9,11 @@ export DB_USER="${DB_USER:-postgres}" export DB_PASSWORD="${DB_PASSWORD:-postgres}" export DB_HOST="${DB_HOST:-localhost}" export DB_PORT="${DB_PORT:-2345}" -export TEST_DB_NAME="${TEST_DB_NAME:-btaa_ogm_api_test}" +export TEST_DB_NAME="${TEST_DB_NAME:-opengeometadata_api_test}" export DATABASE_URL="postgresql+asyncpg://$DB_USER:$DB_PASSWORD@$DB_HOST:$DB_PORT/$TEST_DB_NAME" export ELASTICSEARCH_URL="${ELASTICSEARCH_URL:-http://localhost:9200}" -export ELASTICSEARCH_INDEX="${ELASTICSEARCH_INDEX:-btaa_ogm_api_test}" +export ELASTICSEARCH_INDEX="${ELASTICSEARCH_INDEX:-opengeometadata_api_test}" export REDIS_HOST="${REDIS_HOST:-localhost}" export REDIS_PORT="${REDIS_PORT:-6379}" diff --git a/backend/scripts/simple_bulk_index.py b/backend/scripts/simple_bulk_index.py index 0b269df..59f0ebc 100644 --- a/backend/scripts/simple_bulk_index.py +++ b/backend/scripts/simple_bulk_index.py @@ -111,7 +111,7 @@ def build_suggest_field(doc): async def main(): - index_name = os.getenv("ELASTICSEARCH_INDEX", "btaa_geospatial_api") + index_name = os.getenv("ELASTICSEARCH_INDEX", "opengeometadata_api") chunk_size = 500 logger.info("=" * 70) diff --git a/backend/scripts/start_web_singlehost.sh b/backend/scripts/start_web_singlehost.sh index a4c39b2..957d636 100644 --- a/backend/scripts/start_web_singlehost.sh +++ b/backend/scripts/start_web_singlehost.sh @@ -44,37 +44,37 @@ echo "[start_web_singlehost] configuring nginx SSR upstream for ${WEB_SSR_WORKER } > /etc/nginx/ssr-upstream.conf echo "[start_web_singlehost] configuring nginx frontend API key map" -if [ -n "${BTAA_GEOSPATIAL_API_KEY:-}" ]; then - ESCAPED_API_KEY="$(printf '%s' "${BTAA_GEOSPATIAL_API_KEY}" | sed 's/[$\\"]/\\&/g')" +if [ -n "${OPENGEOMETADATA_API_KEY:-}" ]; then + ESCAPED_API_KEY="$(printf '%s' "${OPENGEOMETADATA_API_KEY}" | sed 's/[$\\"]/\\&/g')" { - echo "map \$http_x_api_key \$btaa_search_results_api_key {" + echo "map \$http_x_api_key \$ogm_search_results_api_key {" echo " default \$http_x_api_key;" printf ' "" "%s";\n' "${ESCAPED_API_KEY}" echo "}" echo - echo "map \$http_x_api_key \$btaa_search_results_turnstile_gate {" + echo "map \$http_x_api_key \$ogm_search_results_turnstile_gate {" echo ' default "";' echo ' "" "frontend-search";' echo "}" echo - echo "map \$http_x_api_key \$btaa_search_results_client_channel {" + echo "map \$http_x_api_key \$ogm_search_results_client_channel {" echo ' default "";' echo ' "" "browser";' echo "}" } > /etc/nginx/frontend-api-key-map.conf else { - echo "map \$http_x_api_key \$btaa_search_results_api_key {" + echo "map \$http_x_api_key \$ogm_search_results_api_key {" echo " default \$http_x_api_key;" echo ' "" "";' echo "}" echo - echo "map \$http_x_api_key \$btaa_search_results_turnstile_gate {" + echo "map \$http_x_api_key \$ogm_search_results_turnstile_gate {" echo ' default "";' echo ' "" "frontend-search";' echo "}" echo - echo "map \$http_x_api_key \$btaa_search_results_client_channel {" + echo "map \$http_x_api_key \$ogm_search_results_client_channel {" echo ' default "";' echo ' "" "browser";' echo "}" diff --git a/backend/scripts/test_gazetteer_api.py b/backend/scripts/test_gazetteer_api.py index 8cce801..0f586e6 100755 --- a/backend/scripts/test_gazetteer_api.py +++ b/backend/scripts/test_gazetteer_api.py @@ -3,7 +3,7 @@ Test script for gazetteer API endpoints. This script provides a comprehensive test suite for the gazetteer API endpoints. -It tests multiple gazetteer sources (GeoNames, Who's on First, BTAA) and provides +It tests multiple gazetteer sources (GeoNames, Who's on First, OGM) and provides detailed output of the test results. The script can be configured to test different environments by specifying a custom base URL. @@ -36,7 +36,7 @@ def test_endpoints(base_url="http://localhost:8000/api/v1"): 2. Search GeoNames 3. Search Who's on First 4. Get WOF details - 5. Search BTAA + 5. Search OGM 6. Search all gazetteers Args: @@ -113,10 +113,10 @@ def test_endpoints(base_url="http://localhost:8000/api/v1"): else: print("Skipping test 4 as no WOF results were returned in test 3") - # Test 5: Search BTAA - print("\nTest 5: Search BTAA") + # Test 5: Search OGM + print("\nTest 5: Search OGM") print("-" * 80) - response = requests.get(f"{base_url}/gazetteers/btaa", params={"q": "minnesota", "limit": 5}) + response = requests.get(f"{base_url}/gazetteers/ogm", params={"q": "minnesota", "limit": 5}) if response.status_code == 200: data = response.json() results = data.get("data", []) diff --git a/backend/scripts/validate_elasticsearch_production.py b/backend/scripts/validate_elasticsearch_production.py index a68da4c..145472e 100755 --- a/backend/scripts/validate_elasticsearch_production.py +++ b/backend/scripts/validate_elasticsearch_production.py @@ -29,7 +29,7 @@ # Use ELASTICSEARCH_URL from environment or default ELASTICSEARCH_URL = os.getenv("ELASTICSEARCH_URL", "http://localhost:9200") -INDEX_NAME = os.getenv("ELASTICSEARCH_INDEX", "btaa_geospatial_api") +INDEX_NAME = os.getenv("ELASTICSEARCH_INDEX", "opengeometadata_api") # Add project root to path sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) diff --git a/backend/scripts/validate_spatial_facets.py b/backend/scripts/validate_spatial_facets.py index 3a32c90..2934aa3 100755 --- a/backend/scripts/validate_spatial_facets.py +++ b/backend/scripts/validate_spatial_facets.py @@ -65,7 +65,7 @@ async def validate_spatial_facets(sample_size: int = 100, verbose: bool = False) for resource in resources: try: # Get the resource from Elasticsearch - es_response = await es.get(index="btaa_ogm_api", id=resource["id"]) + es_response = await es.get(index="opengeometadata_api", id=resource["id"]) es_doc = es_response["_source"] diff --git a/backend/scripts/verify_h3_index.py b/backend/scripts/verify_h3_index.py index 7e76017..5fe42a1 100644 --- a/backend/scripts/verify_h3_index.py +++ b/backend/scripts/verify_h3_index.py @@ -25,7 +25,7 @@ async def main() -> None: - index = os.getenv("ELASTICSEARCH_INDEX", "btaa_geospatial_api") + index = os.getenv("ELASTICSEARCH_INDEX", "opengeometadata_api") # 1. Sample docs: fetch a few with H3 + geo fields sample = await es.search( diff --git a/backend/static/btaa-gin-white.png b/backend/static/btaa-gin-white.png deleted file mode 100644 index d4df6410d6bd4e21547c31c8bc199ea4738ec493..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30416 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[CMefMy@KAf0Zd(sPkjU{{EZknA8R6 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%0DDa4=g{=X>FQ%b~7waF>c_Qdj zKR$yfMrErzk#DOD=)*i0&BF<_P8@*uaw4o-r2gI+^be~>FiR5d7fGN+!9B30A+-)hzP33)*|=mXWmZ7Wg%C$uhGJ}v!9+2 zuZz|ldjdYO)tya63ArCG*6yu;#=;U$hGabY@L9b8kSq7T>FqQ#%JJ+i$&O_s@;6m& zdS%!c!*C5BX;3Dc(~1WrUWMHWm))Y(Ci%a6aOaT20IXAV2kFFcqrZ6Z)lx9KIGc%X zl0zUAEMTF=%R;XGeWA)_^CAr$>>DKhB7KNxfqCl%DT_+R=mj4x?KRuc0B;JO*UHmTxddap2oO!)<2#+SVg*A@_o?%0ZHseDF6Tf diff --git a/backend/templates/docs.html b/backend/templates/docs.html index e208b80..e390221 100644 --- a/backend/templates/docs.html +++ b/backend/templates/docs.html @@ -64,7 +64,7 @@

Project

Tracked OGM Repositories (JSON)
  • - Upstream BTAA Backend Repository + Upstream backend repository
  • Harvesting

    @@ -79,7 +79,7 @@

    Harvesting

    Notes

    • - This repo carries an OGM-branded backend mirror so it can track upstream BTAA API improvements while focusing its public behavior on OpenGeoMetadata harvesting. + This repo carries an OGM-branded backend mirror so it can track upstream OGM API improvements while focusing its public behavior on OpenGeoMetadata harvesting.
    diff --git a/backend/tests/README.md b/backend/tests/README.md index cb8e89e..fbd971c 100644 --- a/backend/tests/README.md +++ b/backend/tests/README.md @@ -1,6 +1,6 @@ -# Testing the BTAA Geoportal API +# Testing the OpenGeoMetadata API API -This directory contains tests for the BTAA Geoportal API. The tests are organized by component and use pytest as the test runner. +This directory contains tests for the OpenGeoMetadata API API. The tests are organized by component and use pytest as the test runner. ## Test Structure diff --git a/backend/tests/api/test_analytics_cli_events.py b/backend/tests/api/test_analytics_cli_events.py index 5eb9933..6ccc37f 100644 --- a/backend/tests/api/test_analytics_cli_events.py +++ b/backend/tests/api/test_analytics_cli_events.py @@ -22,10 +22,10 @@ def delay(self, payload): response = client.post( "/api/v1/analytics/events", headers={ - "X-BTAA-Client-Name": "btaa-geo-api-cli", - "X-BTAA-Client-Version": "0.1.0", - "X-BTAA-Client-Channel": "cli", - "X-BTAA-Client-Instance": "instance-1", + "X-OGM-Client-Name": "ogm-api-cli", + "X-OGM-Client-Version": "0.1.0", + "X-OGM-Client-Channel": "cli", + "X-OGM-Client-Instance": "instance-1", }, json={ "events": [ @@ -50,5 +50,5 @@ def delay(self, payload): event = captured["payload"]["events"][0] search = captured["payload"]["searches"][0] assert event["event_type"] == "cli.command.search" - assert event["client_name"] == "btaa-geo-api-cli" + assert event["client_name"] == "ogm-api-cli" assert search["client_channel"] == "cli" diff --git a/backend/tests/api/test_error_contracts.py b/backend/tests/api/test_error_contracts.py index 23fb7de..cd877af 100644 --- a/backend/tests/api/test_error_contracts.py +++ b/backend/tests/api/test_error_contracts.py @@ -245,7 +245,7 @@ def test_openapi_documents_public_success_and_error_schemas(): ("/api/v1/map/h3", "get"): "#/components/schemas/MapH3Response", ("/api/v1/ogc/collections", "get"): "#/components/schemas/OGCCollectionsResponse", ( - "/api/v1/ogc/collections/btaa-records/items", + "/api/v1/ogc/collections/ogm-records/items", "get", ): "#/components/schemas/OGCFeatureCollectionResponse", ("/api/v1/ogm/repos", "get"): "#/components/schemas/OGMRepoSummariesResponse", diff --git a/backend/tests/api/test_ogc.py b/backend/tests/api/test_ogc.py index 22d35cd..ec99fe4 100644 --- a/backend/tests/api/test_ogc.py +++ b/backend/tests/api/test_ogc.py @@ -28,18 +28,18 @@ def test_ogc_collections(): assert response.status_code == 200 data = response.json() assert "collections" in data - assert data["collections"][0]["id"] == "btaa-records" + assert data["collections"][0]["id"] == "ogm-records" def test_ogc_collection(): - response = client.get("/api/v1/ogc/collections/btaa-records") + response = client.get("/api/v1/ogc/collections/ogm-records") assert response.status_code == 200 data = response.json() - assert data["id"] == "btaa-records" + assert data["id"] == "ogm-records" def test_ogc_queryables(): - response = client.get("/api/v1/ogc/collections/btaa-records/queryables") + response = client.get("/api/v1/ogc/collections/ogm-records/queryables") assert response.status_code == 200 data = response.json() assert data["$schema"] == "https://json-schema.org/draft/2019-09/schema" @@ -47,7 +47,7 @@ def test_ogc_queryables(): def test_ogc_sortables(): - response = client.get("/api/v1/ogc/collections/btaa-records/sortables") + response = client.get("/api/v1/ogc/collections/ogm-records/sortables") assert response.status_code == 200 data = response.json() assert "properties" in data @@ -72,7 +72,7 @@ def test_ogc_items(mock_search_service_class): ], } - response = client.get("/api/v1/ogc/collections/btaa-records/items?q=test&limit=10&sortby=title") + response = client.get("/api/v1/ogc/collections/ogm-records/items?q=test&limit=10&sortby=title") assert response.status_code == 200 data = response.json() @@ -100,7 +100,7 @@ def test_ogc_item(mock_search_service_class): } } - response = client.get("/api/v1/ogc/collections/btaa-records/items/test-123") + response = client.get("/api/v1/ogc/collections/ogm-records/items/test-123") assert response.status_code == 200 data = response.json() diff --git a/backend/tests/api/v1/test_analytics_events.py b/backend/tests/api/v1/test_analytics_events.py index c3ada94..836c611 100644 --- a/backend/tests/api/v1/test_analytics_events.py +++ b/backend/tests/api/v1/test_analytics_events.py @@ -50,11 +50,11 @@ async def test_analytics_events_endpoint_queues_normalized_batch(async_client): content=json.dumps(payload), headers={ "Content-Type": "text/plain;charset=UTF-8", - "Origin": "https://geo.btaa.org", + "Origin": "https://ogm.geo4lib.app", "X-Visit-Token": "visit-123", - "X-BTAA-Client-Name": "geoportal-web", - "X-BTAA-Client-Version": "test-build", - "X-BTAA-Client-Channel": "browser", + "X-OGM-Client-Name": "geoportal-web", + "X-OGM-Client-Version": "test-build", + "X-OGM-Client-Channel": "browser", }, ) @@ -65,7 +65,7 @@ async def test_analytics_events_endpoint_queues_normalized_batch(async_client): assert queued["searches"][0]["client_name"] == "geoportal-web" assert queued["searches"][0]["client_version"] == "test-build" assert queued["searches"][0]["client_channel"] == "browser" - assert queued["searches"][0]["source_host"] == "geo.btaa.org" + assert queued["searches"][0]["source_host"] == "ogm.geo4lib.app" assert queued["impressions"][0]["visit_token"] == "visit-123" assert queued["events"][0]["client_name"] == "geoportal-web" diff --git a/backend/tests/api/v1/test_endpoint_modules_gazetteer_extra.py b/backend/tests/api/v1/test_endpoint_modules_gazetteer_extra.py index fa7bd1e..5de36ad 100644 --- a/backend/tests/api/v1/test_endpoint_modules_gazetteer_extra.py +++ b/backend/tests/api/v1/test_endpoint_modules_gazetteer_extra.py @@ -63,7 +63,7 @@ async def fake_resp(name): monkeypatch.setattr(gaz, "search_geonames", lambda request, q, limit, o: fake_resp("geonames")) monkeypatch.setattr(gaz, "search_wof", lambda request, q, limit, o: fake_resp("wof")) - monkeypatch.setattr(gaz, "search_btaa", lambda request, q, limit, o: fake_resp("btaa")) + monkeypatch.setattr(gaz, "search_ogm", lambda request, q, limit, o: fake_resp("ogm")) class DummyRequest: def __init__(self): @@ -81,9 +81,9 @@ def url(self): # Function may be cached and return JSONResponse; handle both if hasattr(combined, "body"): data = json.loads(combined.body) - assert set(data.keys()) == {"geonames", "wof", "btaa"} + assert set(data.keys()) == {"geonames", "wof", "ogm"} else: - assert set(combined.keys()) == {"geonames", "wof", "btaa"} + assert set(combined.keys()) == {"geonames", "wof", "ogm"} @pytest.mark.asyncio @@ -167,7 +167,7 @@ def url(self): @pytest.mark.asyncio -async def test_search_btaa_success(monkeypatch): +async def test_search_ogm_success(monkeypatch): from app.api.v1.endpoint_modules import gazetteer as gaz async def fake_fetch_all(query): @@ -179,19 +179,19 @@ class DummyRequest: def __init__(self): from starlette.datastructures import URL - self._url = URL("http://test/gazetteers/btaa/search?q=abc") + self._url = URL("http://test/gazetteers/ogm/search?q=abc") self.query_params = "q=abc&limit=10&offset=0" @property def url(self): return self._url - resp = await gaz.search_btaa(request=DummyRequest(), q="abc", limit=10, offset=0) + resp = await gaz.search_ogm(request=DummyRequest(), q="abc", limit=10, offset=0) assert hasattr(resp, "body") @pytest.mark.asyncio -async def test_search_btaa_error(monkeypatch): +async def test_search_ogm_error(monkeypatch): from fastapi import HTTPException from app.api.v1.endpoint_modules import gazetteer as gaz @@ -205,12 +205,12 @@ class DummyRequest: def __init__(self): from starlette.datastructures import URL - self._url = URL("http://test/gazetteers/btaa/search") + self._url = URL("http://test/gazetteers/ogm/search") @property def url(self): return self._url with pytest.raises(HTTPException) as exc: - await gaz.search_btaa(request=DummyRequest(), q="x", limit=10, offset=0) + await gaz.search_ogm(request=DummyRequest(), q="x", limit=10, offset=0) assert exc.value.status_code == 500 diff --git a/backend/tests/api/v1/test_endpoint_modules_gazetteer_production.py b/backend/tests/api/v1/test_endpoint_modules_gazetteer_production.py index be303b9..d5964fb 100644 --- a/backend/tests/api/v1/test_endpoint_modules_gazetteer_production.py +++ b/backend/tests/api/v1/test_endpoint_modules_gazetteer_production.py @@ -42,7 +42,7 @@ def test_search_all_gazetteers_with_real_database(self): data = response.json() assert "geonames" in data assert "wof" in data - assert "btaa" in data + assert "ogm" in data def test_search_geonames_with_real_database(self): """Test search GeoNames with real database connection.""" @@ -70,9 +70,9 @@ def test_search_wof_with_real_database(self): assert "jsonapi" in data assert "links" in data - def test_search_btaa_with_real_database(self): - """Test search BTAA with real database connection.""" - response = client.get("/gazetteers/btaa/search?q=test") + def test_search_ogm_with_real_database(self): + """Test search OGM with real database connection.""" + response = client.get("/gazetteers/ogm/search?q=test") # Should return either success or database error assert response.status_code in [200, 500] @@ -97,9 +97,9 @@ def test_search_specific_gazetteer_wof(self): # Should return either success or database error assert response.status_code in [200, 500] - def test_search_specific_gazetteer_btaa(self): - """Test search with specific gazetteer=btaa.""" - response = client.get("/gazetteers/search?q=test&gazetteer=btaa") + def test_search_specific_gazetteer_ogm(self): + """Test search with specific gazetteer=ogm.""" + response = client.get("/gazetteers/search?q=test&gazetteer=ogm") # Should return either success or database error assert response.status_code in [200, 500] @@ -166,9 +166,9 @@ def test_search_wof_with_pagination(self): response = client.get("/gazetteers/wof/search?q=test&limit=5&offset=0") assert response.status_code in [200, 500] - def test_search_btaa_with_pagination(self): - """Test BTAA search with pagination.""" - response = client.get("/gazetteers/btaa/search?q=test&limit=5&offset=0") + def test_search_ogm_with_pagination(self): + """Test OGM search with pagination.""" + response = client.get("/gazetteers/ogm/search?q=test&limit=5&offset=0") assert response.status_code in [200, 500] def test_search_geonames_with_jsonp(self): @@ -181,9 +181,9 @@ def test_search_wof_with_jsonp(self): response = client.get("/gazetteers/wof/search?q=test&callback=testCallback") assert response.status_code in [200, 500] - def test_search_btaa_with_jsonp(self): - """Test BTAA search with JSONP callback.""" - response = client.get("/gazetteers/btaa/search?q=test&callback=testCallback") + def test_search_ogm_with_jsonp(self): + """Test OGM search with JSONP callback.""" + response = client.get("/gazetteers/ogm/search?q=test&callback=testCallback") assert response.status_code in [200, 500] def test_search_different_query_types(self): @@ -243,7 +243,7 @@ def test_search_database_connection_handling(self): "/gazetteers/search?q=test", "/gazetteers/geonames/search?q=test", "/gazetteers/wof/search?q=test", - "/gazetteers/btaa/search?q=test", + "/gazetteers/ogm/search?q=test", ] for endpoint in endpoints: @@ -259,7 +259,7 @@ def test_search_response_structure_validation(self): data = response.json() # Should have proper structure for each gazetteer - for gazetteer_name in ["geonames", "wof", "btaa"]: + for gazetteer_name in ["geonames", "wof", "ogm"]: if gazetteer_name in data: gazetteer_data = data[gazetteer_name] assert isinstance(gazetteer_data, dict) @@ -295,9 +295,9 @@ def test_search_wof_response_structure(self): assert "data" in data assert "links" in data - def test_search_btaa_response_structure(self): - """Test BTAA search response structure.""" - response = client.get("/gazetteers/btaa/search?q=test") + def test_search_ogm_response_structure(self): + """Test OGM search response structure.""" + response = client.get("/gazetteers/ogm/search?q=test") if response.status_code == 200: data = response.json() @@ -344,7 +344,7 @@ def test_search_async_operations(self): "/gazetteers/search?q=test", "/gazetteers/geonames/search?q=test", "/gazetteers/wof/search?q=test", - "/gazetteers/btaa/search?q=test", + "/gazetteers/ogm/search?q=test", ] for endpoint in async_endpoints: @@ -363,7 +363,7 @@ def test_search_sql_query_execution(self): data = response.json() assert "geonames" in data assert "wof" in data - assert "btaa" in data + assert "ogm" in data def test_search_service_integration(self): """Test integration with real database services.""" @@ -382,4 +382,4 @@ def test_search_data_processing(self): # Should have processed the database results properly assert "geonames" in data assert "wof" in data - assert "btaa" in data + assert "ogm" in data diff --git a/backend/tests/api/v1/test_endpoint_modules_shapefiles_simple.py b/backend/tests/api/v1/test_endpoint_modules_shapefiles_simple.py index d72811e..a85c74f 100644 --- a/backend/tests/api/v1/test_endpoint_modules_shapefiles_simple.py +++ b/backend/tests/api/v1/test_endpoint_modules_shapefiles_simple.py @@ -54,7 +54,7 @@ def test_duckdb_path_configuration(self): """Test that DuckDB path is properly configured.""" import os - expected_path = os.getenv("DUCKDB_DATABASE_PATH", "data/duckdb/btaa_ogm_api.duckdb") + expected_path = os.getenv("DUCKDB_DATABASE_PATH", "data/duckdb/ogm_ogm_api.duckdb") assert DUCKDB_DATABASE_PATH == expected_path def test_endpoint_paths(self): diff --git a/backend/tests/api/v1/test_gazetteer.py b/backend/tests/api/v1/test_gazetteer.py index 0166e7c..a3620d7 100644 --- a/backend/tests/api/v1/test_gazetteer.py +++ b/backend/tests/api/v1/test_gazetteer.py @@ -181,12 +181,12 @@ def test_get_wof_by_id_with_callback(self): assert data.endswith(")") -class TestBTAAGazetteer: - """Test cases for BTAA gazetteer endpoints.""" +class TestOGMGazetteer: + """Test cases for OGM gazetteer endpoints.""" - def test_list_btaa_success(self): - """Test successful listing of BTAA records.""" - response = client.get("/gazetteers/gazetteers/btaa") + def test_list_ogm_success(self): + """Test successful listing of OGM records.""" + response = client.get("/gazetteers/gazetteers/ogm") assert response.status_code in [200, 500] data = response.json() @@ -198,9 +198,9 @@ def test_list_btaa_success(self): else: assert "detail" in data - def test_list_btaa_with_pagination(self): - """Test BTAA listing with pagination.""" - response = client.get("/gazetteers/gazetteers/btaa?page=1&per_page=10") + def test_list_ogm_with_pagination(self): + """Test OGM listing with pagination.""" + response = client.get("/gazetteers/gazetteers/ogm?page=1&per_page=10") assert response.status_code in [200, 500] data = response.json() @@ -209,9 +209,9 @@ def test_list_btaa_with_pagination(self): assert "data" in data assert "meta" in data - def test_list_btaa_with_search(self): - """Test BTAA listing with search query.""" - response = client.get("/gazetteers/gazetteers/btaa?q=minnesota") + def test_list_ogm_with_search(self): + """Test OGM listing with search query.""" + response = client.get("/gazetteers/gazetteers/ogm?q=minnesota") assert response.status_code in [200, 500] data = response.json() @@ -220,9 +220,9 @@ def test_list_btaa_with_search(self): assert "data" in data assert "meta" in data - def test_list_btaa_with_callback(self): - """Test BTAA listing with JSONP callback.""" - response = client.get("/gazetteers/gazetteers/btaa?callback=myCallback") + def test_list_ogm_with_callback(self): + """Test OGM listing with JSONP callback.""" + response = client.get("/gazetteers/gazetteers/ogm?callback=myCallback") assert response.status_code in [200, 500] data = response.json() @@ -388,9 +388,9 @@ def test_wof_response_content(self): assert "attributes" in first_item assert first_item["type"] == "wof" - def test_btaa_response_content(self): - """Test BTAA response content structure.""" - response = client.get("/gazetteers/gazetteers/btaa") + def test_ogm_response_content(self): + """Test OGM response content structure.""" + response = client.get("/gazetteers/gazetteers/ogm") assert response.status_code in [200, 500] data = response.json() @@ -401,7 +401,7 @@ def test_btaa_response_content(self): assert "id" in first_item assert "type" in first_item assert "attributes" in first_item - assert first_item["type"] == "btaa" + assert first_item["type"] == "ogm" class TestGazetteerErrorHandling: diff --git a/backend/tests/api/v1/test_gazetteer_endpoints.py b/backend/tests/api/v1/test_gazetteer_endpoints.py index e3d385e..8c0d9ec 100644 --- a/backend/tests/api/v1/test_gazetteer_endpoints.py +++ b/backend/tests/api/v1/test_gazetteer_endpoints.py @@ -36,7 +36,7 @@ def test_list_gazetteers(): # Verify gazetteer data structure (without checking specific record counts) geonames = next(g for g in data["data"] if g["id"] == "geonames") wof = next(g for g in data["data"] if g["id"] == "wof") - btaa = next(g for g in data["data"] if g["id"] == "btaa") + ogm = next(g for g in data["data"] if g["id"] == "ogm") assert geonames["attributes"]["name"] == "GeoNames" assert "record_count" in geonames["attributes"] @@ -45,8 +45,8 @@ def test_list_gazetteers(): assert "record_count" in wof["attributes"] assert "additional_tables" in wof["attributes"] - assert btaa["attributes"]["name"] == "BTAA" - assert "record_count" in btaa["attributes"] + assert ogm["attributes"]["name"] == "OGM" + assert "record_count" in ogm["attributes"] else: # If the endpoint fails due to database connection issues, that's okay for now # The important thing is that the endpoint structure is correct @@ -97,10 +97,10 @@ def test_search_wof(): assert response.status_code in [200, 500] # Allow both success and database errors -def test_search_btaa(): - """Test the search_btaa endpoint structure.""" +def test_search_ogm(): + """Test the search_ogm endpoint structure.""" # Call endpoint with query params - response = client.get("/api/v1/gazetteers/btaa/search?q=Minnesota&limit=10") + response = client.get("/api/v1/gazetteers/ogm/search?q=Minnesota&limit=10") # For now, just verify the endpoint exists and returns a response # The actual database calls may fail in the test environment @@ -112,7 +112,7 @@ def test_search_btaa(): assert "type" in data["data"][0] assert "id" in data["data"][0] assert "attributes" in data["data"][0] - assert data["data"][0]["type"] == "btaa" + assert data["data"][0]["type"] == "ogm" else: # If the endpoint fails due to database issues, that's okay for now # The important thing is that the endpoint structure is correct @@ -132,10 +132,10 @@ def test_search_all_gazetteers(): # The response should contain results from all gazetteers assert "geonames" in data assert "wof" in data - assert "btaa" in data + assert "ogm" in data # Each gazetteer should have a data field - for gazetteer in ["geonames", "wof", "btaa"]: + for gazetteer in ["geonames", "wof", "ogm"]: if data[gazetteer]["data"]: # If there are results assert "type" in data[gazetteer]["data"][0] assert "id" in data[gazetteer]["data"][0] @@ -159,7 +159,7 @@ def test_search_specific_gazetteer(): # Should only return geonames results assert "geonames" in data assert "wof" not in data - assert "btaa" not in data + assert "ogm" not in data # If there are results, verify the structure if data["geonames"]["data"]: @@ -318,7 +318,7 @@ def test_gazetteer_endpoints_structure(self): assert "/api/v1/gazetteers/search" in routes assert "/api/v1/gazetteers/geonames/search" in routes assert "/api/v1/gazetteers/wof/search" in routes - assert "/api/v1/gazetteers/btaa/search" in routes + assert "/api/v1/gazetteers/ogm/search" in routes assert "/api/v1/gazetteers/nominatim/search" in routes @patch("app.api.v1.endpoint_modules.gazetteer.database") @@ -349,9 +349,9 @@ def test_list_gazetteers_success(self, mock_database): assert gazetteers["wof"]["attributes"]["record_count"] == 200 assert "additional_tables" in gazetteers["wof"]["attributes"] - assert "btaa" in gazetteers - assert gazetteers["btaa"]["attributes"]["name"] == "BTAA" - assert gazetteers["btaa"]["attributes"]["record_count"] == 50 + assert "ogm" in gazetteers + assert gazetteers["ogm"]["attributes"]["name"] == "OGM" + assert gazetteers["ogm"]["attributes"]["record_count"] == 50 @patch("app.api.v1.endpoint_modules.gazetteer.database") def test_list_gazetteers_database_error(self, mock_database): @@ -418,10 +418,10 @@ def test_search_all_gazetteers_success(self): if response.status_code == 200: assert "geonames" in data assert "wof" in data - assert "btaa" in data + assert "ogm" in data # Verify each section has the expected structure - for gazetteer_name in ["geonames", "wof", "btaa"]: + for gazetteer_name in ["geonames", "wof", "ogm"]: gazetteer_data = data[gazetteer_name] assert "data" in gazetteer_data assert isinstance(gazetteer_data["data"], list) @@ -448,7 +448,7 @@ def test_search_all_gazetteers_specific_geonames(self): assert "data" in data assert isinstance(data["data"], list) assert "wof" not in data - assert "btaa" not in data + assert "ogm" not in data def test_search_geonames_success(self): """Test successful GeoNames search.""" @@ -522,10 +522,10 @@ def test_search_wof_database_error(self, mock_database): ) assert "Database error" not in response.text - def test_search_btaa_success(self): - """Test successful BTAA search.""" + def test_search_ogm_success(self): + """Test successful OGM search.""" # Use real data instead of mocks - response = client.get("/api/v1/gazetteers/btaa/search?q=test") + response = client.get("/api/v1/gazetteers/ogm/search?q=test") # The endpoint might return 500 due to event loop issues, so check for success or error assert response.status_code in [200, 500] @@ -542,11 +542,11 @@ def test_search_btaa_success(self): assert "attributes" in result @patch("app.api.v1.endpoint_modules.gazetteer.database") - def test_search_btaa_database_error(self, mock_database): - """Test BTAA search with database error.""" + def test_search_ogm_database_error(self, mock_database): + """Test OGM search with database error.""" mock_database.fetch_all.side_effect = Exception("Database error") - response = client.get("/api/v1/gazetteers/btaa/search?q=test") + response = client.get("/api/v1/gazetteers/ogm/search?q=test") assert response.status_code == 500 data = response.json() @@ -598,11 +598,11 @@ def test_search_wof_empty_results(self, mock_database): assert len(data["data"]) == 0 @patch("app.api.v1.endpoint_modules.gazetteer.database") - def test_search_btaa_empty_results(self, mock_database): - """Test BTAA search with empty results.""" + def test_search_ogm_empty_results(self, mock_database): + """Test OGM search with empty results.""" mock_database.fetch_all = AsyncMock(return_value=[]) - response = client.get("/api/v1/gazetteers/btaa/search?q=nonexistent") + response = client.get("/api/v1/gazetteers/ogm/search?q=nonexistent") assert response.status_code == 200 data = response.json() @@ -626,7 +626,7 @@ def test_gazetteer_search_parameter_validation(self): response = client.get("/api/v1/gazetteers/wof/search") assert response.status_code == 422 - response = client.get("/api/v1/gazetteers/btaa/search") + response = client.get("/api/v1/gazetteers/ogm/search") assert response.status_code == 422 def test_gazetteer_search_limit_validation(self): diff --git a/backend/tests/api/v1/test_home_blog_posts.py b/backend/tests/api/v1/test_home_blog_posts.py index c6e6e28..69a7625 100644 --- a/backend/tests/api/v1/test_home_blog_posts.py +++ b/backend/tests/api/v1/test_home_blog_posts.py @@ -18,7 +18,7 @@ async def mock_list_home_posts(*, limit, pinned_slugs, tag): "excerpt": "Pinned excerpt", "published_at": "2026-02-02T00:00:00", "category": "update", - "authors": ["BTAA-GIN Staff"], + "authors": ["OGM-GIN Staff"], "tags": ["Program Updates"], "image_url": None, "image_alt": None, @@ -33,7 +33,7 @@ async def mock_list_home_posts(*, limit, pinned_slugs, tag): monkeypatch.setattr(home_module.gin_blog_service, "list_home_posts", mock_list_home_posts) - response = await async_client.get("/api/v1/home/blog-posts?limit=2&theme=btaa") + response = await async_client.get("/api/v1/home/blog-posts?limit=2&theme=ogm") assert response.status_code == 200 payload = response.json() assert payload["meta"]["total_count"] == 1 diff --git a/backend/tests/api/v1/test_resource_presenter.py b/backend/tests/api/v1/test_resource_presenter.py index e8b2683..e0abc12 100644 --- a/backend/tests/api/v1/test_resource_presenter.py +++ b/backend/tests/api/v1/test_resource_presenter.py @@ -152,7 +152,7 @@ async def test_resource_presenter_full_profile_contract_snapshot(): }, "meta": { "@context": "https://gin.btaa.org/ld/contexts/ogm-aardvark-btaa.context.jsonld", - "@type": "BtaaAardvarkRecord", + "@type": "OgmAardvarkRecord", "ui": { "thumbnail_url": immutable_thumbnail_url, "citation": "APA", @@ -245,7 +245,7 @@ async def test_resource_presenter_search_profile_contract_snapshot(): }, "meta": { "@context": "https://gin.btaa.org/ld/contexts/ogm-aardvark-btaa.context.jsonld", - "@type": "BtaaAardvarkRecord", + "@type": "OgmAardvarkRecord", "ui": { "thumbnail_url": "https://images.example.edu/res-1-thumb.jpg", "citation": "APA", @@ -303,7 +303,7 @@ async def test_resource_presenter_homepage_profile_contract_snapshot(): }, "meta": { "@context": "https://gin.btaa.org/ld/contexts/ogm-aardvark-btaa.context.jsonld", - "@type": "BtaaAardvarkRecord", + "@type": "OgmAardvarkRecord", "ui": { "thumbnail_url": "https://images.example.edu/res-1-thumb.jpg", "viewer": { diff --git a/backend/tests/api/v1/test_resource_thumbnail_endpoints.py b/backend/tests/api/v1/test_resource_thumbnail_endpoints.py index 2d3e7bc..8a212f1 100644 --- a/backend/tests/api/v1/test_resource_thumbnail_endpoints.py +++ b/backend/tests/api/v1/test_resource_thumbnail_endpoints.py @@ -211,7 +211,7 @@ def test_bridge_thumbnail_asset_used_when_no_intrinsic_source( mock_session.return_value.__aenter__.return_value = mock_session_instance resource_id = "test-bridge-asset" - asset_url = "https://geobtaa-assets-prod.s3.us-east-2.amazonaws.com/store/asset/x/thumb.png" + asset_url = "https://geoogm-assets-prod.s3.us-east-2.amazonaws.com/store/asset/x/thumb.png" image_hash = _remote_thumbnail_image_hash(asset_url) mock_row = _resource_row(resource_id, "{}") mock_result = MagicMock() diff --git a/backend/tests/api/v1/test_slack_endpoints.py b/backend/tests/api/v1/test_slack_endpoints.py index d2f0499..85ced55 100644 --- a/backend/tests/api/v1/test_slack_endpoints.py +++ b/backend/tests/api/v1/test_slack_endpoints.py @@ -58,4 +58,4 @@ def test_slack_command_dispatches_valid_signed_payload(client, monkeypatch): assert response.status_code == 200 assert response.json()["response_type"] == "ephemeral" - assert "BTAA Geoportal" in response.json()["blocks"][0]["text"]["text"] + assert "OpenGeoMetadata API" in response.json()["blocks"][0]["text"]["text"] diff --git a/backend/tests/api/v1/test_turnstile_endpoints.py b/backend/tests/api/v1/test_turnstile_endpoints.py index c10008d..1765284 100644 --- a/backend/tests/api/v1/test_turnstile_endpoints.py +++ b/backend/tests/api/v1/test_turnstile_endpoints.py @@ -55,7 +55,7 @@ async def create_session(self, request): attributes = response.json()["data"]["attributes"] assert attributes["verified"] is True assert attributes["session_token"] == "session-token" - assert "btaa_turnstile_session=session-token" in response.headers["set-cookie"] + assert "ogm_turnstile_session=session-token" in response.headers["set-cookie"] def test_turnstile_verify_rejects_failed_validation(monkeypatch): diff --git a/backend/tests/db/test_async_engine.py b/backend/tests/db/test_async_engine.py index 414763d..f498b66 100644 --- a/backend/tests/db/test_async_engine.py +++ b/backend/tests/db/test_async_engine.py @@ -19,7 +19,7 @@ async def test_create_app_async_engine_forces_nullpool_in_tests(monkeypatch): monkeypatch.setenv("SQLALCHEMY_ASYNC_USE_NULLPOOL", "false") engine = create_app_async_engine( - "postgresql+asyncpg://postgres:postgres@localhost:2345/btaa_geospatial_api_test" + "postgresql+asyncpg://postgres:postgres@localhost:2345/opengeometadata_api_test" ) try: @@ -33,7 +33,7 @@ def test_create_app_sync_engine_forces_nullpool_in_tests(monkeypatch): monkeypatch.setenv("SQLALCHEMY_SYNC_USE_NULLPOOL", "false") engine = create_app_sync_engine( - "postgresql://postgres:postgres@localhost:2345/btaa_geospatial_api_test" + "postgresql://postgres:postgres@localhost:2345/opengeometadata_api_test" ) try: @@ -51,7 +51,7 @@ def test_create_app_sync_engine_uses_env_pool_bounds(monkeypatch): monkeypatch.setenv("SQLALCHEMY_SYNC_POOL_TIMEOUT", "3") engine = create_app_sync_engine( - "postgresql://postgres:postgres@localhost:2345/btaa_geospatial_api_test" + "postgresql://postgres:postgres@localhost:2345/opengeometadata_api_test" ) try: @@ -72,7 +72,7 @@ async def test_create_app_async_engine_uses_env_pool_bounds(monkeypatch): monkeypatch.setenv("SQLALCHEMY_ASYNC_POOL_TIMEOUT", "3") engine = create_app_async_engine( - "postgresql+asyncpg://postgres:postgres@localhost:2345/btaa_geospatial_api_test" + "postgresql+asyncpg://postgres:postgres@localhost:2345/opengeometadata_api_test" ) try: diff --git a/backend/tests/elasticsearch/test_mappings.py b/backend/tests/elasticsearch/test_mappings.py index aa272ab..a63942b 100644 --- a/backend/tests/elasticsearch/test_mappings.py +++ b/backend/tests/elasticsearch/test_mappings.py @@ -77,12 +77,12 @@ def test_facet_field_mappings(self): assert "geo_county" in properties assert properties["geo_county"]["type"] == "keyword" - def test_btaa_specific_field_mappings(self): - """Test that BTAA-specific field mappings are present.""" + def test_ogm_specific_field_mappings(self): + """Test that OGM-specific field mappings are present.""" properties = INDEX_MAPPING["mappings"]["properties"] - # Test BTAA fields - btaa_fields = [ + # Test OGM fields + ogm_fields = [ "b1g_code_s", "b1g_status_s", "b1g_dct_accrualMethod_s", @@ -105,8 +105,8 @@ def test_btaa_specific_field_mappings(self): "b1g_adminTags_sm", ] - for field in btaa_fields: - assert field in properties, f"BTAA field {field} should be in mappings" + for field in ogm_fields: + assert field in properties, f"OGM field {field} should be in mappings" def test_suggest_field_mapping(self): """Test that the suggest field is correctly configured for autocomplete.""" diff --git a/backend/tests/elasticsearch/test_search.py b/backend/tests/elasticsearch/test_search.py index e87b198..f582225 100644 --- a/backend/tests/elasticsearch/test_search.py +++ b/backend/tests/elasticsearch/test_search.py @@ -804,13 +804,13 @@ async def test_search_resources_geo_filter_reduces_results_and_matches_pg(self, "total": {"value": 5, "relation": "eq"}, "hits": [ { - "_index": "btaa_ogm_api", + "_index": "opengeometadata_api", "_id": "baseline-1", "_score": 1.0, "_source": {"id": "baseline-1"}, }, { - "_index": "btaa_ogm_api", + "_index": "opengeometadata_api", "_id": "baseline-2", "_score": 0.9, "_source": {"id": "baseline-2"}, @@ -827,7 +827,7 @@ async def test_search_resources_geo_filter_reduces_results_and_matches_pg(self, "total": {"value": 1, "relation": "eq"}, "hits": [ { - "_index": "btaa_ogm_api", + "_index": "opengeometadata_api", "_id": "geo-1", "_score": 1.0, "_source": {"id": "geo-1"}, diff --git a/backend/tests/gazetteer/test_import_all.py b/backend/tests/gazetteer/test_import_all.py index 96d4fbd..8a992a3 100644 --- a/backend/tests/gazetteer/test_import_all.py +++ b/backend/tests/gazetteer/test_import_all.py @@ -54,12 +54,12 @@ def test_module_imports(self): def test_importer_imports(self): """Test that importer classes can be imported.""" try: - from app.gazetteer.importers.btaa_importer import BtaaImporter from app.gazetteer.importers.fast_importer import FastImporter from app.gazetteer.importers.geonames_importer import GeonamesImporter + from app.gazetteer.importers.ogm_importer import OgmImporter from app.gazetteer.importers.wof_importer import WofImporter - assert callable(BtaaImporter) + assert callable(OgmImporter) assert callable(FastImporter) assert callable(GeonamesImporter) assert callable(WofImporter) diff --git a/backend/tests/integration/test_mkdocs_interactive_examples.py b/backend/tests/integration/test_mkdocs_interactive_examples.py index ea9d9c6..fb27dbc 100644 --- a/backend/tests/integration/test_mkdocs_interactive_examples.py +++ b/backend/tests/integration/test_mkdocs_interactive_examples.py @@ -21,7 +21,7 @@ URBAN_BASE_LAYERS_COLLECTION_ID = "b1g_urbanBaseLayers" DISPLAY_NOTE_PREFIX_REGRESSION_RESOURCE_ID = "b1g_2Lx2SCAOw85E" DISPLAY_NOTE_PREFIX_REGRESSION_BODY = ( - "This dataset is a historical version held by the BTAA-GIN. " + "This dataset is a historical version held by the OGM-GIN. " "For the most current layer, consult Open Data Minneapolis" ) _LAST_LIVE_REQUEST_AT: float | None = None @@ -204,8 +204,8 @@ def _is_frontend_turnstile_gate(response: requests.Response) -> bool: visible_text = _visible_text_from_html(response.text) return ( "Browser verification" in visible_text - and "Continue to the BTAA Geoportal" in visible_text - and "Complete the verification check to continue to the BTAA Geoportal" in visible_text + and "Continue to the OpenGeoMetadata API" in visible_text + and "Complete the verification check to continue to the OpenGeoMetadata API" in visible_text ) diff --git a/backend/tests/middleware/test_rate_limit_integration.py b/backend/tests/middleware/test_rate_limit_integration.py index 78bf8f3..f092a54 100644 --- a/backend/tests/middleware/test_rate_limit_integration.py +++ b/backend/tests/middleware/test_rate_limit_integration.py @@ -137,8 +137,8 @@ async def fake_get_tier_info(self, api_key, request_ip): # pragma: no cover - s if api_key == "frontend-server-key": return { "tier_id": None, - "tier_name": "btaa_primary", - "display_name": "BTAA Geoportal Frontend", + "tier_name": "ogm_primary", + "display_name": "OpenGeoMetadata API Frontend", "requests_per_minute": None, "api_key_id": None, "key_hash": "frontend-server-key-hash", diff --git a/backend/tests/middleware/test_rate_limit_middleware.py b/backend/tests/middleware/test_rate_limit_middleware.py index 216c625..93e45d7 100644 --- a/backend/tests/middleware/test_rate_limit_middleware.py +++ b/backend/tests/middleware/test_rate_limit_middleware.py @@ -173,8 +173,8 @@ def test_is_immutable_asset_route(self, path, expected): ("/api/openapi.json", True), ("/api/redoc", True), ("/static/brand.css", True), - ("/static/btaa-logo-white.png", True), - ("/static/btaa-gin-white.png", True), + ("/static/opengeometadata-bauhaus-logo.svg", True), + ("/static/opengeometadata-map-legend-logo-composite.svg", True), ("/static/favicon.ico", True), ("/api/v1/search", False), ("/api/v1/resources", False), @@ -399,7 +399,7 @@ async def test_dispatch_unlimited_tier(self, middleware): # Mock tier info (unlimited) tier_info = { - "tier_name": "btaa_primary", + "tier_name": "ogm_primary", "requests_per_minute": None, # Unlimited "key_hash": "test_hash", } diff --git a/backend/tests/middleware/test_turnstile_middleware.py b/backend/tests/middleware/test_turnstile_middleware.py index 26234ff..f9f8b80 100644 --- a/backend/tests/middleware/test_turnstile_middleware.py +++ b/backend/tests/middleware/test_turnstile_middleware.py @@ -50,7 +50,7 @@ def test_turnstile_middleware_allows_requests_when_disabled(monkeypatch): ("method", "path", "headers", "params", "json"), [ ("GET", "/api/v1/search", {}, None, None), - ("GET", "/api/v1/search", {"Origin": "https://gin.btaa.org"}, None, None), + ("GET", "/api/v1/search", {"Origin": "https://ogm.geo4lib.app"}, None, None), ( "GET", "/api/v1/search", @@ -61,7 +61,7 @@ def test_turnstile_middleware_allows_requests_when_disabled(monkeypatch): ( "GET", "/api/v1/search", - {"Referer": "https://gin.btaa.org/api/specification/endpoints/"}, + {"Referer": "https://ogm.geo4lib.app/api/docs"}, None, None, ), @@ -75,7 +75,7 @@ def test_turnstile_middleware_allows_requests_when_disabled(monkeypatch): ( "GET", "/api/v1/search", - {"X-BTAA-Client-Channel": "documentation"}, + {"X-OGM-Client-Channel": "documentation"}, None, None, ), @@ -123,7 +123,7 @@ async def session_invalid(self, request): monkeypatch.setattr(TurnstileService, "is_session_valid", session_invalid) client = TestClient(_make_app(), base_url="http://localhost") - response = client.get("/api/v1/search", headers={"X-BTAA-Client-Channel": "browser"}) + response = client.get("/api/v1/search", headers={"X-OGM-Client-Channel": "browser"}) assert response.status_code == 200 @@ -141,7 +141,7 @@ async def session_invalid(self, request): monkeypatch.setattr(TurnstileService, "is_session_valid", session_invalid) client = TestClient(_make_app(), base_url="http://localhost") - response = client.get("/api/v1/search", headers={"X-BTAA-Client-Channel": "browser"}) + response = client.get("/api/v1/search", headers={"X-OGM-Client-Channel": "browser"}) assert response.status_code == 403 assert response.json()["error"] == "turnstile_required" @@ -156,7 +156,7 @@ async def session_valid(self, request): monkeypatch.setattr(TurnstileService, "is_session_valid", session_valid) client = TestClient(_make_app()) - response = client.get("/api/v1/search", headers={"X-BTAA-Client-Channel": "browser"}) + response = client.get("/api/v1/search", headers={"X-OGM-Client-Channel": "browser"}) assert response.status_code == 200 @@ -187,8 +187,8 @@ async def session_invalid(self, request): response = client.get( "/api/v1/search", headers={ - "X-BTAA-Client-Name": "btaa-geo-api-cli", - "X-BTAA-Client-Channel": "cli", + "X-OGM-Client-Name": "ogm-api-cli", + "X-OGM-Client-Channel": "cli", }, ) @@ -206,7 +206,7 @@ async def session_invalid(self, request): client = TestClient(_make_app()) response = client.get( "/api/v1/search", - headers={"User-Agent": "BTAA-Geo-API-CLI/0.1.0"}, + headers={"User-Agent": "OGM-Geo-API-CLI/0.1.0"}, ) assert response.status_code == 200 @@ -223,7 +223,7 @@ async def session_invalid(self, request): client = TestClient(_make_app()) response = client.get( "/api/v1/search", - headers={"User-Agent": "BTAA-QGIS-Plugin/0.1.0"}, + headers={"User-Agent": "OGM-QGIS-Plugin/0.1.0"}, ) assert response.status_code == 200 @@ -232,31 +232,31 @@ async def session_invalid(self, request): @pytest.mark.parametrize( ("headers", "params"), [ - ({"X-BTAA-Client-Channel": "browser"}, None), - ({"X-BTAA-Turnstile-Gate": "frontend-search"}, None), + ({"X-OGM-Client-Channel": "browser"}, None), + ({"X-OGM-Turnstile-Gate": "frontend-search"}, None), ({"X-Visit-Token": "visit-token"}, None), ( { "Origin": "https://lib-geoportal-prd-web-01.oit.umn.edu", - "X-BTAA-Client-Channel": "browser", + "X-OGM-Client-Channel": "browser", }, None, ), ( { "X-API-Key": "frontend-key", - "X-BTAA-Turnstile-Gate": "frontend-search", + "X-OGM-Turnstile-Gate": "frontend-search", }, None, ), ( { - "X-BTAA-Client-Channel": "script", - "X-BTAA-Turnstile-Gate": "frontend-search", + "X-OGM-Client-Channel": "script", + "X-OGM-Turnstile-Gate": "frontend-search", }, None, ), - ({"X-BTAA-Client-Channel": "browser"}, {"api_key": "frontend-key"}), + ({"X-OGM-Client-Channel": "browser"}, {"api_key": "frontend-key"}), ], ) def test_turnstile_middleware_challenges_frontend_gate_requests_without_session( diff --git a/backend/tests/scripts/test_backup_elasticsearch.py b/backend/tests/scripts/test_backup_elasticsearch.py index 72811dd..f2265d8 100644 --- a/backend/tests/scripts/test_backup_elasticsearch.py +++ b/backend/tests/scripts/test_backup_elasticsearch.py @@ -5,7 +5,7 @@ def test_repository_body_builds_s3_settings(monkeypatch): monkeypatch.setattr(backup, "REPOSITORY_TYPE", "s3") - monkeypatch.setattr(backup, "BACKUP_S3_PREFIX", "btaa-geospatial-api") + monkeypatch.setattr(backup, "BACKUP_S3_PREFIX", "opengeometadata-api") monkeypatch.setenv("BACKUP_S3_BUCKET", "geoportal-dr") monkeypatch.setenv("KAMAL_DEST", "prd") monkeypatch.setenv("ELASTICSEARCH_SNAPSHOT_S3_STORAGE_CLASS", "STANDARD_IA") @@ -17,7 +17,7 @@ def test_repository_body_builds_s3_settings(monkeypatch): "type": "s3", "settings": { "bucket": "geoportal-dr", - "base_path": "btaa-geospatial-api/prd/elasticsearch", + "base_path": "opengeometadata-api/prd/elasticsearch", "client": "default", "compress": True, "storage_class": "STANDARD_IA", diff --git a/backend/tests/scripts/test_backup_postgres_to_s3.py b/backend/tests/scripts/test_backup_postgres_to_s3.py index 47ef073..6c7e68b 100644 --- a/backend/tests/scripts/test_backup_postgres_to_s3.py +++ b/backend/tests/scripts/test_backup_postgres_to_s3.py @@ -8,16 +8,16 @@ def test_normalize_database_url_strips_async_driver(): assert ( backup._normalize_database_url( - "postgresql+asyncpg://postgres:secret@paradedb:5432/btaa_geospatial_api" + "postgresql+asyncpg://postgres:secret@paradedb:5432/opengeometadata_api" ) - == "postgresql://postgres:secret@paradedb:5432/btaa_geospatial_api" + == "postgresql://postgres:secret@paradedb:5432/opengeometadata_api" ) def test_s3_key_joins_and_strips_slashes(): assert ( - backup._s3_key("/btaa-geospatial-api/", "prd", "/postgres/", "dump.dump") - == "btaa-geospatial-api/prd/postgres/dump.dump" + backup._s3_key("/opengeometadata-api/", "prd", "/postgres/", "dump.dump") + == "opengeometadata-api/prd/postgres/dump.dump" ) @@ -25,14 +25,14 @@ def test_pg_connection_args_keep_password_out_of_args(monkeypatch): monkeypatch.setenv("EXISTING_ENV", "kept") connection = backup._pg_connection_args( - "postgresql://postgres:p%40ss@paradedb:5432/btaa_geospatial_api" + "postgresql://postgres:p%40ss@paradedb:5432/opengeometadata_api" ) assert connection.args == [ "--host", "paradedb", "--dbname", - "btaa_geospatial_api", + "opengeometadata_api", "--port", "5432", "--username", @@ -65,7 +65,7 @@ def test_prune_old_backups_keeps_newest_count(monkeypatch, tmp_path: Path): config = backup.BackupConfig( destination="prd", bucket="bucket", - prefix="btaa-geospatial-api", + prefix="opengeometadata-api", retention_count=3, database_url="postgresql://postgres:secret@db/example", work_dir=tmp_path, diff --git a/backend/tests/scripts/test_render_cron_env.py b/backend/tests/scripts/test_render_cron_env.py index 95d78a5..fef3083 100644 --- a/backend/tests/scripts/test_render_cron_env.py +++ b/backend/tests/scripts/test_render_cron_env.py @@ -9,7 +9,7 @@ def test_render_cron_env_exports_filters_and_quotes_values(): rendered = render_cron_env_exports( { "CRON_LOCAL_TIMEZONE": "America/Chicago", - "DATABASE_URL": "postgresql://user:p@ss word@db.example/btaa", + "DATABASE_URL": "postgresql://user:p@ss word@db.example/ogm", "GITHUB_TOKEN": "ghp_example", "REDIS_HOST": "redis.internal", "OGM_NIGHTLY_CRON_ENABLED": "false", @@ -21,7 +21,7 @@ def test_render_cron_env_exports_filters_and_quotes_values(): ) assert "export CRON_LOCAL_TIMEZONE=America/Chicago" in rendered - assert "export DATABASE_URL='postgresql://user:p@ss word@db.example/btaa'" in rendered + assert "export DATABASE_URL='postgresql://user:p@ss word@db.example/ogm'" in rendered assert "export GITHUB_TOKEN=ghp_example" in rendered assert "export REDIS_HOST=redis.internal" in rendered assert "export OGM_NIGHTLY_CRON_ENABLED=false" in rendered diff --git a/backend/tests/services/test_api_key_service.py b/backend/tests/services/test_api_key_service.py index 5010daf..150e328 100644 --- a/backend/tests/services/test_api_key_service.py +++ b/backend/tests/services/test_api_key_service.py @@ -4,14 +4,19 @@ import hashlib import uuid +from datetime import datetime import pytest +from sqlalchemy import select from app.services import api_key_service as api_key_service_module from app.services.api_key_service import ( API_KEY_HASH_ITERATIONS, APIKeyService, ) +from db.migrations.initialize_api_tiers import initialize_api_tiers +from db.models import api_keys, api_service_tiers +from db.session import async_session @pytest.mark.unit @@ -73,6 +78,70 @@ def test_hash_api_key_different_keys(self, api_key_service): assert hash1 != hash2 + def test_legacy_default_hash_remains_available_for_key_migration( + self, api_key_service, monkeypatch + ): + """The identity rename must not invalidate keys stored with the old fallback salt.""" + monkeypatch.delenv("API_KEY_HASH_SECRET", raising=False) + monkeypatch.delenv("SECRET_KEY", raising=False) + + key = "pre-ogm-api-key" + + assert api_key_service.hash_api_key(key) != api_key_service.legacy_default_hash_api_key(key) + + @pytest.mark.asyncio + async def test_validate_api_key_upgrades_legacy_fallback_hash( + self, api_key_service, monkeypatch + ): + """A stored pre-OGM fallback hash is accepted and rewritten in place.""" + monkeypatch.delenv("API_KEY_HASH_SECRET", raising=False) + monkeypatch.delenv("SECRET_KEY", raising=False) + initialize_api_tiers() + + key = "pre-ogm-key-to-upgrade" + old_hash = api_key_service.legacy_default_hash_api_key(key) + new_hash = api_key_service.hash_api_key(key) + now = datetime.utcnow() + + async with async_session() as session: + tier_id = ( + await session.execute( + select(api_service_tiers.c.id).where( + api_service_tiers.c.tier_name == "anonymous" + ) + ) + ).scalar_one() + key_id = ( + await session.execute( + api_keys.insert() + .values( + key_hash=old_hash, + tier_id=tier_id, + name="legacy fallback migration test", + is_active=True, + created_at=now, + updated_at=now, + ) + .returning(api_keys.c.id) + ) + ).scalar_one() + await session.commit() + + tier = await api_key_service.validate_api_key(key) + + assert tier is not None + assert tier["tier_name"] == "anonymous" + assert tier["key_hash"] == new_hash + + async with async_session() as session: + stored_hash = ( + await session.execute(select(api_keys.c.key_hash).where(api_keys.c.id == key_id)) + ).scalar_one() + await session.execute(api_keys.delete().where(api_keys.c.id == key_id)) + await session.commit() + + assert stored_hash == new_hash + def test_cache_lookup_key_does_not_store_raw_key(self, api_key_service): """Cache keys should not retain the plaintext API key.""" lookup_key = api_key_service._cache_lookup_key("secret-api-key") @@ -85,7 +154,7 @@ async def test_configured_server_api_key_is_unlimited_without_database( self, api_key_service, monkeypatch ): """The deployment frontend key should not depend on destination-local DB rows.""" - monkeypatch.setenv("BTAA_GEOSPATIAL_API_KEY", "frontend-server-key") + monkeypatch.setenv("OPENGEOMETADATA_API_KEY", "frontend-server-key") class ExplodingSessionFactory: def __call__(self): @@ -99,8 +168,8 @@ def __call__(self): ) assert tier is not None - assert tier["tier_name"] == "btaa_primary" - assert tier["display_name"] == "BTAA Geoportal Frontend" + assert tier["tier_name"] == "ogm_primary" + assert tier["display_name"] == "OpenGeoMetadata API Frontend" assert tier["requests_per_minute"] is None assert tier["api_key_id"] is None assert tier["key_hash"] == api_key_service.legacy_hash_api_key("frontend-server-key") @@ -111,7 +180,7 @@ def test_cached_tier_returns_copy_and_expires(self, api_key_service, monkeypatch monkeypatch.setattr(api_key_service_module, "API_KEY_TIER_CACHE_TTL_SECONDS", 60) monkeypatch.setattr(api_key_service_module.time, "monotonic", lambda: now) - api_key_service._set_cached_tier("cache-key", {"tier_id": 1, "tier_name": "btaa"}) + api_key_service._set_cached_tier("cache-key", {"tier_id": 1, "tier_name": "ogm"}) cached_tier = api_key_service._get_cached_tier("cache-key", None) cached_tier["tier_id"] = 999 @@ -131,7 +200,7 @@ def test_cached_tier_still_enforces_allowed_ips(self, api_key_service, monkeypat "cache-key", { "tier_id": 1, - "tier_name": "btaa", + "tier_name": "ogm", "allowed_ips": ["192.0.2.10"], }, ) diff --git a/backend/tests/services/test_api_usage_log_service.py b/backend/tests/services/test_api_usage_log_service.py index abb6c23..8cf1b04 100644 --- a/backend/tests/services/test_api_usage_log_service.py +++ b/backend/tests/services/test_api_usage_log_service.py @@ -14,14 +14,14 @@ def _build_request(): method="GET", headers={ "User-Agent": "TestAgent/1.0", - "Referer": "https://geo.btaa.org/search?q=maps", + "Referer": "https://ogm.geo4lib.app/search?q=maps", "X-Visit-Token": "visit-123", "X-Forwarded-For": "203.0.113.10, 10.0.0.2", - "Origin": "https://geo.btaa.org", - "X-BTAA-Client-Name": "geoportal-web", - "X-BTAA-Client-Version": "test-build", - "X-BTAA-Client-Channel": "browser", - "X-BTAA-Client-Instance": "dev-local", + "Origin": "https://ogm.geo4lib.app", + "X-OGM-Client-Name": "geoportal-web", + "X-OGM-Client-Version": "test-build", + "X-OGM-Client-Channel": "browser", + "X-OGM-Client-Instance": "dev-local", }, query_params=QueryParams("q=maps&utm_source=geoportal&utm_campaign=spring-launch"), client=SimpleNamespace(host="127.0.0.1"), @@ -55,16 +55,16 @@ async def test_log_request_queues_celery_payload(monkeypatch): assert payload["response_time_ms"] == 87 assert payload["ip_address"] == "203.0.113.10" assert payload["visit_token"] == "visit-123" - assert payload["referring_domain"] == "geo.btaa.org" + assert payload["referring_domain"] == "ogm.geo4lib.app" assert payload["utm_source"] == "geoportal" assert payload["utm_campaign"] == "spring-launch" assert payload["properties"]["query_params"] == {"q": "maps"} - assert payload["properties"]["origin"] == "https://geo.btaa.org" + assert payload["properties"]["origin"] == "https://ogm.geo4lib.app" assert payload["client_name"] == "geoportal-web" assert payload["client_version"] == "test-build" assert payload["client_channel"] == "browser" assert payload["client_instance"] == "dev-local" - assert payload["source_host"] == "geo.btaa.org" + assert payload["source_host"] == "ogm.geo4lib.app" assert ( payload["partition_month"] == datetime.fromisoformat(payload["requested_at"]).date().replace(day=1).isoformat() diff --git a/backend/tests/services/test_api_usage_log_service_cli.py b/backend/tests/services/test_api_usage_log_service_cli.py index d7227a4..cb99730 100644 --- a/backend/tests/services/test_api_usage_log_service_cli.py +++ b/backend/tests/services/test_api_usage_log_service_cli.py @@ -12,11 +12,11 @@ def test_cli_headers_and_api_key_id_are_preserved_in_usage_log_payload(): request.client.host = "192.0.2.10" request.query_params = {"q": "water"} request.headers = { - "User-Agent": "BTAA-Geo-API-CLI/0.1.0", - "X-BTAA-Client-Name": "btaa-geo-api-cli", - "X-BTAA-Client-Version": "0.1.0", - "X-BTAA-Client-Channel": "cli", - "X-BTAA-Client-Instance": "instance-1", + "User-Agent": "OGM-Geo-API-CLI/0.1.0", + "X-OGM-Client-Name": "ogm-api-cli", + "X-OGM-Client-Version": "0.1.0", + "X-OGM-Client-Channel": "cli", + "X-OGM-Client-Instance": "instance-1", } payload = APIUsageLogService()._build_log_entry( @@ -29,8 +29,8 @@ def test_cli_headers_and_api_key_id_are_preserved_in_usage_log_payload(): assert payload["api_key_id"] == 42 assert payload["tier_id"] == 7 - assert payload["client_name"] == "btaa-geo-api-cli" + assert payload["client_name"] == "ogm-api-cli" assert payload["client_channel"] == "cli" assert payload["client_instance"] == "instance-1" - assert payload["user_agent"] == "BTAA-Geo-API-CLI/0.1.0" + assert payload["user_agent"] == "OGM-Geo-API-CLI/0.1.0" assert payload["properties"]["query_params"] == {"q": "water"} diff --git a/backend/tests/services/test_bridge_search_index.py b/backend/tests/services/test_bridge_search_index.py index e19cf49..1c07634 100644 --- a/backend/tests/services/test_bridge_search_index.py +++ b/backend/tests/services/test_bridge_search_index.py @@ -53,7 +53,7 @@ async def fake_process_resource(row): monkeypatch.setenv("BRIDGE_SEARCH_INDEX_REFRESH_ENABLED", "true") monkeypatch.setenv("BRIDGE_SEARCH_INDEX_MAX_RESOURCE_IDS", "1") - monkeypatch.setenv("ELASTICSEARCH_INDEX", "btaa_geospatial_api") + monkeypatch.setenv("ELASTICSEARCH_INDEX", "opengeometadata_api") monkeypatch.setattr(search_index, "database", fake_database) monkeypatch.setattr(search_index, "es", fake_es) monkeypatch.setattr(search_index, "process_resource", fake_process_resource) @@ -77,7 +77,7 @@ async def fake_process_resource(row): "resource-2", ] assert fake_database.fetch_calls == 3 - assert fake_es.indices.refreshed == ["btaa_geospatial_api"] + assert fake_es.indices.refreshed == ["opengeometadata_api"] @pytest.mark.asyncio diff --git a/backend/tests/services/test_bridge_sync_service.py b/backend/tests/services/test_bridge_sync_service.py index 7814391..a5f03e3 100644 --- a/backend/tests/services/test_bridge_sync_service.py +++ b/backend/tests/services/test_bridge_sync_service.py @@ -50,7 +50,7 @@ def fetch_record(self, resource_id): return self.records.get(resource_id) -def _http_error(status_code: int, url: str = "https://geo.btaa.org/api/kithe_bridge/test"): +def _http_error(status_code: int, url: str = "https://ogm.geo4lib.app/api/kithe_bridge/test"): response = requests.Response() response.status_code = status_code response.url = url diff --git a/backend/tests/services/test_citation_formats_service.py b/backend/tests/services/test_citation_formats_service.py index f5ab8c7..e117e0c 100644 --- a/backend/tests/services/test_citation_formats_service.py +++ b/backend/tests/services/test_citation_formats_service.py @@ -38,7 +38,7 @@ def test_json_ld_minimal_dataset(self): assert ld["url"] == "https://geo.example.org/resources/test-uuid-123" assert ld["@id"] == ld["url"] assert ld["publisher"]["name"] == "University of Test" - assert ld["includedInDataCatalog"]["name"] == "Big Ten Academic Alliance Geoportal" + assert ld["includedInDataCatalog"]["name"] == "OpenGeoMetadata API" def test_json_ld_map_type(self): doc = { @@ -150,7 +150,7 @@ def test_mla_format(self): mla = svc.get_citation("mla") assert "Doe, Jane." in mla assert '"Historic Map."' in mla - assert "Big Ten Academic Alliance Geoportal" in mla + assert "OpenGeoMetadata API" in mla def test_chicago_format(self): from app.services.citation_service import CitationService @@ -165,7 +165,7 @@ def test_chicago_format(self): assert "Author" in chicago assert "2024" in chicago assert "Chicago Test" in chicago - assert "Big Ten Academic Alliance" in chicago + assert "OpenGeoMetadata" in chicago def test_get_all_citations(self): from app.services.citation_service import CitationService diff --git a/backend/tests/services/test_feedback_service.py b/backend/tests/services/test_feedback_service.py index d1b52e2..e1eb711 100644 --- a/backend/tests/services/test_feedback_service.py +++ b/backend/tests/services/test_feedback_service.py @@ -67,7 +67,7 @@ def fake_run(cmd, *, input, check, timeout): } assert calls[0]["cmd"] == ["/usr/local/bin/sendmail", "-t", "-i"] assert calls[0]["check"] is True - assert b"BTAA Geoportal Feedback: Question" in calls[0]["input"] + assert b"OpenGeoMetadata API Feedback: Question" in calls[0]["input"] assert b"Can this record link to a newer dataset?" in calls[0]["input"] diff --git a/backend/tests/services/test_ogm_field_mapper.py b/backend/tests/services/test_ogm_field_mapper.py index 7eb8d2a..b1b0b82 100644 --- a/backend/tests/services/test_ogm_field_mapper.py +++ b/backend/tests/services/test_ogm_field_mapper.py @@ -31,11 +31,11 @@ def test_field_mapping_contains_standard_ogm_fields(self): assert "gbl_resourcetype_sm" in mapping assert mapping["gbl_resourcetype_sm"] == "gbl_resourceType_sm" - def test_field_mapping_contains_btaa_specific_fields(self): - """Test that the field mapping contains BTAA-specific fields.""" + def test_field_mapping_contains_ogm_specific_fields(self): + """Test that the field mapping contains OGM-specific fields.""" mapping = OGMFieldMapper.FIELD_MAPPING - # Test some BTAA-specific fields + # Test some OGM-specific fields assert "b1g_code_s" in mapping assert mapping["b1g_code_s"] == "b1g_code_s" # No change needed @@ -137,7 +137,7 @@ def test_map_resource_fields_comprehensive(self): "gbl_suppressed_b": False, "gbl_georeferenced_b": True, "gbl_displaynote_sm": "Test note", - # BTAA-specific fields + # OGM-specific fields "b1g_code_s": "BTA-001", "b1g_status_s": "active", "b1g_dct_accrualmethod_s": "RPA", @@ -192,7 +192,7 @@ def test_map_resource_fields_comprehensive(self): assert result["gbl_georeferenced_b"] is True assert result["gbl_displayNote_sm"] == "Test note" - # Verify BTAA fields + # Verify OGM fields assert result["b1g_dct_accrualMethod_s"] == "RPA" assert result["b1g_dct_accrualPeriodicity_s"] == "irregular" assert result["b1g_dateAccessioned_s"] == "2023-01-01" diff --git a/backend/tests/services/test_rate_limit_service.py b/backend/tests/services/test_rate_limit_service.py index 5a37fb4..1cd9861 100644 --- a/backend/tests/services/test_rate_limit_service.py +++ b/backend/tests/services/test_rate_limit_service.py @@ -30,7 +30,7 @@ def rate_limit_service(self): async def test_check_rate_limit_unlimited_tier(self, rate_limit_service): """Test that unlimited tiers always allow requests.""" allowed, remaining, reset_time = await rate_limit_service.check_rate_limit( - "btaa_primary", "test_identifier", None + "ogm_primary", "test_identifier", None ) assert allowed is True assert remaining == -1 # -1 indicates unlimited @@ -89,7 +89,7 @@ async def test_check_rate_limit_no_redis(self, rate_limit_service): async def test_get_rate_limit_headers_unlimited(self, rate_limit_service): """Test rate limit headers for unlimited tier.""" headers = await rate_limit_service.get_rate_limit_headers( - "btaa_primary", "test_identifier", None + "ogm_primary", "test_identifier", None ) assert headers["X-RateLimit-Limit"] == "unlimited" diff --git a/backend/tests/services/test_search_service.py b/backend/tests/services/test_search_service.py index 44ac856..a50b0b7 100644 --- a/backend/tests/services/test_search_service.py +++ b/backend/tests/services/test_search_service.py @@ -71,10 +71,10 @@ def test_search_service_initialization(self): assert hasattr(service, "es") # In test environment, the index name might be different assert service.index_name in [ - "btaa_geospatial_api", - "btaa_geospatial_api_test", - "btaa_ogm_api_test", - "btaa_ogm_api", + "opengeometadata_api", + "opengeometadata_api_test", + "opengeometadata_api_test", + "opengeometadata_api", "opengeometadata_api_test", "opengeometadata_api", ] @@ -688,7 +688,7 @@ def test_extract_filter_queries_all_aggregation_fields(self): "fq[language_agg][]=English&" "fq[creator_agg][]=Test Creator&" "fq[provider_agg][]=Test Provider&" - "fq[b1g_code_s][]=BTAA&" + "fq[b1g_code_s][]=OGM&" "fq[access_rights_agg][]=Public&" "fq[georeferenced_agg][]=true&" "fq[map_overlay_agg][]=true&" @@ -708,7 +708,7 @@ def test_extract_filter_queries_all_aggregation_fields(self): assert result["b1g_language_sm"] == ["English"] assert result["dct_creator_sm"] == ["Test Creator"] assert result["schema_provider_s"] == ["Test Provider"] - assert result["b1g_code_s"] == ["BTAA"] + assert result["b1g_code_s"] == ["OGM"] assert result["dct_accessRights_s"] == ["Public"] assert result["gbl_georeferenced_b"] == ["true"] assert result["b1g_georeferenced_allmaps_b"] == ["true"] diff --git a/backend/tests/tasks/test_worker_fetch_and_cache_image.py b/backend/tests/tasks/test_worker_fetch_and_cache_image.py index 81f39a3..cfa4a0c 100644 --- a/backend/tests/tasks/test_worker_fetch_and_cache_image.py +++ b/backend/tests/tasks/test_worker_fetch_and_cache_image.py @@ -249,7 +249,7 @@ def test_unr_level_zero_info_worker_fetches_and_caches_real_rendition(): mock_get.assert_called_once_with( UNR_IMAGE_URL, timeout=30, - headers={"User-Agent": "BTAA-Geospatial-Data-API/1.0 (https://geo.btaa.org/)"}, + headers={"User-Agent": "OpenGeoMetadata-API/1.0 (https://opengeometadata.org/)"}, allow_redirects=False, stream=True, ) diff --git a/backend/tests/test_ogm_field_mapper.py b/backend/tests/test_ogm_field_mapper.py index 78fd1e4..741d826 100644 --- a/backend/tests/test_ogm_field_mapper.py +++ b/backend/tests/test_ogm_field_mapper.py @@ -57,9 +57,9 @@ def test_map_resource_fields_standard_fields(self): result = OGMFieldMapper.map_resource_fields(db_resource) assert result == expected - def test_map_resource_fields_btaa_fields(self): - """Test mapping of BTAA-specific fields.""" - # Database response with BTAA fields + def test_map_resource_fields_ogm_fields(self): + """Test mapping of OGM-specific fields.""" + # Database response with OGM fields db_resource = { "id": "test-123", "b1g_code_s": "TEST001", @@ -83,7 +83,7 @@ def test_map_resource_fields_btaa_fields(self): "b1g_admintags_sm": ["tag1", "tag2"], } - # Expected result with proper BTAA field names + # Expected result with proper OGM field names expected = { "id": "test-123", "b1g_code_s": "TEST001", @@ -111,7 +111,7 @@ def test_map_resource_fields_btaa_fields(self): assert result == expected def test_map_resource_fields_mixed_fields(self): - """Test mapping with both standard and BTAA fields.""" + """Test mapping with both standard and OGM fields.""" db_resource = { "id": "test-123", "dct_title_s": "Test Title", diff --git a/config/geobtaa-backend-source.env b/config/geobtaa-backend-source.env deleted file mode 100644 index 4e40f66..0000000 --- a/config/geobtaa-backend-source.env +++ /dev/null @@ -1,15 +0,0 @@ -# Canonical upstream source for the mirrored backend tree. -# -# scripts/sync_backend_from_data_api.sh reads these defaults. The import metadata -# at the bottom is updated by the script after a successful --apply run. - -GEOBTAA_API_REMOTE_NAME=upstream -GEOBTAA_API_REMOTE_URL=https://github.com/geobtaa/api.git -GEOBTAA_API_BRANCH=develop -GEOBTAA_API_BACKEND_PREFIX=backend -GEOBTAA_API_BACKEND_SPLIT_BRANCH=vendor/geobtaa-api-backend - -# Updated after an applied import. -GEOBTAA_API_LAST_IMPORT_COMMIT=0c9d80b3b36e833fce0a17551da893bf0aa68928 -GEOBTAA_API_LAST_BACKEND_SPLIT_COMMIT=1cf222b377be4ba1d8468fa864724542e866eb3e -GEOBTAA_API_LAST_IMPORT_AT=2026-06-07T00:01:22Z diff --git a/config/ogm-owned-paths.txt b/config/ogm-owned-paths.txt index 700ccb2..e996568 100644 --- a/config/ogm-owned-paths.txt +++ b/config/ogm-owned-paths.txt @@ -1,7 +1,7 @@ # Paths intentionally owned by this OpenGeoMetadata downstream. # # The upstream backend sync helper protects backend/* entries in this file so -# the BTAA backend import does not silently erase local branding, deployment, +# an upstream backend import does not silently erase local branding, deployment, # and OGM harvesting behavior. Root-level entries document the wider overlay. .github/workflows/ogm-nightly-sync.yml diff --git a/config/upstream-backend-source.env b/config/upstream-backend-source.env new file mode 100644 index 0000000..5fe86fb --- /dev/null +++ b/config/upstream-backend-source.env @@ -0,0 +1,15 @@ +# Canonical upstream source for the mirrored backend tree. +# +# scripts/sync_backend_from_data_api.sh reads these defaults. The import metadata +# at the bottom is updated by the script after a successful --apply run. + +UPSTREAM_API_REMOTE_NAME=upstream +UPSTREAM_API_REMOTE_URL=https://github.com/geobtaa/api.git +UPSTREAM_API_BRANCH=develop +UPSTREAM_API_BACKEND_PREFIX=backend +UPSTREAM_API_BACKEND_SPLIT_BRANCH=vendor/upstream-api-backend + +# Updated after an applied import. +UPSTREAM_API_LAST_IMPORT_COMMIT=0c9d80b3b36e833fce0a17551da893bf0aa68928 +UPSTREAM_API_LAST_BACKEND_SPLIT_COMMIT=1cf222b377be4ba1d8468fa864724542e866eb3e +UPSTREAM_API_LAST_IMPORT_AT=2026-06-07T00:01:22Z diff --git a/docs/backend_upstream_sync.md b/docs/backend_upstream_sync.md index b283347..41a5fc4 100644 --- a/docs/backend_upstream_sync.md +++ b/docs/backend_upstream_sync.md @@ -10,7 +10,7 @@ downstream overlay. - `geobtaa/api` remains the source for shared backend bug fixes and enhancements. - `backend/` is the import boundary. - `config/ogm-owned-paths.txt` lists files this repo owns downstream. -- `config/geobtaa-backend-source.env` records the canonical upstream branch and +- `config/upstream-backend-source.env` records the canonical upstream branch and the last applied import metadata. Do not routinely merge the full `geobtaa/api` repository into this repo. The @@ -78,7 +78,7 @@ git switch -c feature/sync-geobtaa-api-YYYY-MM-DD ./scripts/sync_backend_from_data_api.sh --apply git diff --stat make test -git add backend config/geobtaa-backend-source.env +git add backend config/upstream-backend-source.env git commit -m "Import geobtaa/api backend " ``` diff --git a/docs/gazetteer_api.md b/docs/gazetteer_api.md index 422dc11..c4e470a 100644 --- a/docs/gazetteer_api.md +++ b/docs/gazetteer_api.md @@ -1,6 +1,6 @@ # Gazetteer API Documentation -The OpenGeoMetadata API provides access to multiple gazetteers (GeoNames, Who's on First, BTAA, and FAST) through a unified API. This document describes the available endpoints and how to use them. +The OpenGeoMetadata API provides access to multiple gazetteers (GeoNames, Who's on First, OGM, and FAST) through a unified API. This document describes the available endpoints and how to use them. ## Overview @@ -10,7 +10,7 @@ The API provides the following gazetteer endpoints: - **Search GeoNames**: Search the GeoNames gazetteer - **Search Who's on First**: Search the Who's on First gazetteer - **Get WOF Details**: Get detailed information about a specific Who's on First place -- **Search BTAA**: Search the BTAA gazetteer +- **Search OGM**: Search the OGM gazetteer - **Unified Search**: Search across all gazetteers with a single query All endpoints include caching for improved performance. The cache duration can be configured using the `GAZETTEER_CACHE_TTL` environment variable (default: 3600 seconds/1 hour). @@ -55,13 +55,13 @@ Returns information about all available gazetteers, including record counts. } }, { - "id": "btaa", + "id": "ogm", "type": "gazetteer", "attributes": { - "name": "BTAA", - "description": "Big Ten Academic Alliance Geoportal gazetteer", + "name": "OGM", + "description": "OpenGeoMetadata API gazetteer", "record_count": 5000, - "website": "https://geo.btaa.org/" + "website": "https://opengeometadata.org/" } } ], @@ -306,11 +306,11 @@ GET /api/v1/gazetteers/wof/85977539 } ``` -### Search BTAA +### Search OGM -**Endpoint:** `GET /api/v1/gazetteers/btaa` +**Endpoint:** `GET /api/v1/gazetteers/ogm` -Search for places in the BTAA gazetteer. +Search for places in the OGM gazetteer. **Parameters:** @@ -326,7 +326,7 @@ Search for places in the BTAA gazetteer. **Example Request:** ``` -GET /api/v1/gazetteers/btaa?q=minnesota&limit=5 +GET /api/v1/gazetteers/ogm?q=minnesota&limit=5 ``` **Example Response:** @@ -336,7 +336,7 @@ GET /api/v1/gazetteers/btaa?q=minnesota&limit=5 "data": [ { "id": "1", - "type": "btaa", + "type": "ogm", "attributes": { "fast_area": "Minnesota", "bounding_box": "-97.23,43.50,-89.53,49.38", @@ -376,9 +376,9 @@ Search across all gazetteers with a single query. | Parameter | Type | Description | |-----------|------|-------------| | q | string | Search query (required) | -| gazetteer | string | Specific gazetteer to search (geonames, wof, btaa, or all) | +| gazetteer | string | Specific gazetteer to search (geonames, wof, ogm, or all) | | country_code | string | Two-letter country code (for GeoNames and WOF) | -| state_abbv | string | Two-letter state abbreviation (for BTAA) | +| state_abbv | string | Two-letter state abbreviation (for OGM) | | offset | integer | Result offset for pagination (default: 0) | | limit | integer | Maximum number of results to return (default: 20) | @@ -426,8 +426,8 @@ GET /api/v1/gazetteers/search?q=chicago&limit=5 }, { "id": "123", - "type": "btaa", - "source": "btaa", + "type": "ogm", + "source": "ogm", "attributes": { "fast_area": "Chicago", "state_abbv": "IL", @@ -447,7 +447,7 @@ GET /api/v1/gazetteers/search?q=chicago&limit=5 "gazetteer": null, "country_code": null, "state_abbv": null, - "gazetteers_searched": ["geonames", "wof", "btaa"] + "gazetteers_searched": ["geonames", "wof", "ogm"] } } } @@ -517,7 +517,7 @@ data/ *.csv names/ *.csv - btaa/ + ogm/ *.csv ``` diff --git a/docs/gazetteer_data_management.md b/docs/gazetteer_data_management.md index 9e93dfb..ce5a837 100644 --- a/docs/gazetteer_data_management.md +++ b/docs/gazetteer_data_management.md @@ -9,7 +9,7 @@ The gazetteer data management system consists of two main components: 2. Importers - Scripts to load the prepared data into the database Currently supported gazetteers: -- BTAA Placenames +- OGM Placenames - GeoNames - OCLC FAST Geographic (FAST) - Who's on First (WOF) @@ -32,7 +32,7 @@ data/ wof/ # Who's on First data csv/ # Exported CSV files geonames/ # GeoNames data - btaa/ # BTAA Geoportal data + ogm/ # OpenGeoMetadata API data fast/ # OCLC FAST Geographic data ``` @@ -55,13 +55,13 @@ cd backend && python app/gazetteer/download.py --gazetteers [gazetteer_name] Where `[gazetteer_name]` can be: - `wof` - Who's on First - `geonames` - GeoNames -- `btaa` - BTAA Geoportal +- `ogm` - OpenGeoMetadata API - `fast` - FAST (Faceted Application of Subject Terminology) ### Gazetteer-Specific Notes -#### BTAA Geoportal -1. Downloads BTAA Geoportal data +#### OpenGeoMetadata API +1. Downloads OpenGeoMetadata API data 2. Processes it into the required format #### GeoNames @@ -103,7 +103,7 @@ python app/gazetteer/import_all.py --gazetteers [gazetteer_name] Where `[gazetteer_name]` can be: - `wof` - Who's on First - `geonames` - GeoNames -- `btaa` - BTAA Geoportal +- `ogm` - OpenGeoMetadata API - `fast` - FAST (Faceted Application of Subject Terminology) ### Import Process @@ -126,9 +126,9 @@ For each gazetteer: - Processes tab-delimited text files - Handles specific GeoNames field formats and data types -#### BTAA Geoportal +#### OpenGeoMetadata API - Uses a chunk size of 2000 for optimal performance -- Handles BTAA-specific data formats and fields +- Handles OGM-specific data formats and fields #### FAST (Faceted Application of Subject Terminology) - Parses the MARCXML file using a SAX parser for efficient memory usage diff --git a/docs/repository_branch_inventory.md b/docs/repository_branch_inventory.md index b0a6d12..e60d356 100644 --- a/docs/repository_branch_inventory.md +++ b/docs/repository_branch_inventory.md @@ -39,7 +39,7 @@ decision even though their intended capabilities have been superseded. | Branch | Behind/ahead | Unique commits | Assessment | Recommended disposition | | --- | ---: | --- | --- | --- | | `feature/duckdb-shapefiles` | 97 / 1 | `f3870d0` | Early shapefile endpoint plus a committed 3.4 MB DuckDB file and large lockfile change. Current `develop` has a maintained shapefile service and endpoint test coverage. | Delete after owner confirmation | -| `feature/geosearch` | 97 / 2 | `2277261`, `efce8eb` | Initial bbox search. Current `develop` has normalized bbox filters, scoring, containment behavior, and extensive tests. The same branch also remains in the BTAA upstream remote. | Delete after owner confirmation | +| `feature/geosearch` | 97 / 2 | `2277261`, `efce8eb` | Initial bbox search. Current `develop` has normalized bbox filters, scoring, containment behavior, and extensive tests. The same branch also remains in the upstream remote. | Delete after owner confirmation | | `feature/test-suite` | 174 / 1 | `e2f8305` | Initial test harness with a committed SQLite test database. Current `develop` has 158 backend test modules and a passing 1,831-test suite. | Delete after owner confirmation | If any historical branch must be retained for archaeology, preserve it outside @@ -59,7 +59,7 @@ cleanup. 5. Fetch with pruning into the cutover workstation and verify that local `origin/*` refs exactly match GitHub. 6. Populate the fresh rewrite mirror only from the approved live heads and - tags. Never include `upstream/*` refs or locally fetched BTAA tags. + tags. Never include `upstream/*` refs or locally fetched upstream tags. 7. Record the final ref names and pre-rewrite SHAs in the private change record. The tested rewrite can preserve all 18 branches, so branch pruning is a diff --git a/docs/repository_transfer_rehearsal.md b/docs/repository_transfer_rehearsal.md index 893f11f..80e974e 100644 --- a/docs/repository_transfer_rehearsal.md +++ b/docs/repository_transfer_rehearsal.md @@ -31,7 +31,7 @@ record described by `repository_transfer.md`. The rehearsal populated a new bare repository only from the locally fetched `refs/remotes/origin/*` branches after those refs were compared to live GitHub. -BTAA remote refs and locally fetched BTAA tags were excluded. The temporary +Upstream remote refs and locally fetched upstream tags were excluded. The temporary mirror was rewritten with: ```bash @@ -101,7 +101,7 @@ corrected to preserve the original relative directory sources `esdata`, The same preflight confirmed that OpenGeoMetadata permits Actions for all repositories, standard hosted runners are enabled, no repository in the -organization occupies the BTAA fork network, and the personal GHCR package is +organization occupies the upstream fork network, and the personal GHCR package is private, remains personal-account scoped, and is not linked to this repository. A current logical PostgreSQL dump completed successfully, its permissions were restricted to the deployment user, and `pg_restore --list` validated its diff --git a/docs/scripts.md b/docs/scripts.md index 3c3bafe..7b0105d 100644 --- a/docs/scripts.md +++ b/docs/scripts.md @@ -103,7 +103,7 @@ At the repo root: Imports `geobtaa/api/backend` into this repo's `backend/` directory. It uses `git subtree split` to create a backend-only source branch, protects OpenGeoMetadata-owned backend files listed in `config/ogm-owned-paths.txt`, and -records applied import metadata in `config/geobtaa-backend-source.env`. +records applied import metadata in `config/upstream-backend-source.env`. Dry run: diff --git a/docs/upstream_reconciliation.md b/docs/upstream_reconciliation.md index bd6d727..0a77267 100644 --- a/docs/upstream_reconciliation.md +++ b/docs/upstream_reconciliation.md @@ -1,22 +1,22 @@ -# BTAA backend reconciliation +# Upstream backend reconciliation This repository does not share Git ancestry with `geobtaa/api`. The local `develop` branch and `upstream/develop` have no merge base, so Git ahead/behind counts are not an integration plan. The supported unit of comparison is the -BTAA `backend/` subtree recorded in `config/geobtaa-backend-source.env`. +upstream `backend/` subtree recorded in `config/upstream-backend-source.env`. ## Baseline and target | Item | Value | | --- | --- | -| Last imported BTAA commit | `0c9d80b3b36e833fce0a17551da893bf0aa68928` | -| BTAA tag containing that commit | `v0.7.16` | +| Last imported upstream commit | `0c9d80b3b36e833fce0a17551da893bf0aa68928` | +| Upstream tag containing that commit | `v0.7.16` | | Recorded backend split | `1cf222b377be4ba1d8468fa864724542e866eb3e` | -| Review ceiling | BTAA `0.8.11` (`4254aa3`) | +| Review ceiling | Upstream `0.8.11` (`4254aa3`) | | OGM product version | Independent; currently `0.7.0` | -The review ceiling is not a promise that this product becomes BTAA `0.8.11`. -OGM releases and BTAA source provenance are separate concepts. +The review ceiling is not a promise that this product becomes upstream `0.8.11`. +OGM releases and upstream source provenance are separate concepts. ## Reconciliation policy @@ -30,18 +30,18 @@ OGM releases and BTAA source provenance are separate concepts. `config/ogm-owned-paths.txt` without an explicit OGM design decision. 5. Run focused tests for every port, followed by the complete backend suite before release. -6. Update `config/geobtaa-backend-source.env` only for a complete applied +6. Update `config/upstream-backend-source.env` only for a complete applied subtree import. Selective ports remain documented here instead. ## Baseline CI coverage -The recorded `v0.7.16` backend baseline postdates BTAA commit `44411cf`, which +The recorded `v0.7.16` backend baseline postdates upstream commit `44411cf`, which introduced the repository-level CI workflow. The backend subtree import could not carry that top-level workflow, so this repository now ports its compatible backend job separately in `.github/workflows/ci.yml`. The port runs the complete Python suite with ParadeDB, Elasticsearch, Redis, -parallel workers, a wall-clock watchdog, and a 50% coverage floor. BTAA's +parallel workers, a wall-clock watchdog, and a 50% coverage floor. Upstream's frontend, CLI, QGIS plugin, and MkDocs jobs are intentionally excluded because those products are not present in this backend-only repository. Action dependencies are pinned to immutable commits to satisfy the transfer-readiness @@ -51,35 +51,35 @@ policy. | Upstream commit | Decision | OGM rationale or follow-up | | --- | --- | --- | -| `e95178a` Prepare GTM for production cutover | **Ported selectively** | Ported the recursive test route helper needed by FastAPI's lazy `include_router` representation and updated route-registration assertions. BTAA frontend analytics and production configuration remain not applicable. | +| `e95178a` Prepare GTM for production cutover | **Ported selectively** | Ported the recursive test route helper needed by FastAPI's lazy `include_router` representation and updated route-registration assertions. Upstream frontend analytics and production configuration remain not applicable. | | `1433322` Bump API version to 0.8.0 | Not applicable | OGM product versioning is independent. | -| `a529b3b` Prepare v0.8.1 release | Not applicable | BTAA release bookkeeping is not imported. | +| `a529b3b` Prepare v0.8.1 release | Not applicable | Upstream release bookkeeping is not imported. | | `a17b83e` Fix production feedback delivery default | Not applicable | Recipient and mail defaults are operator-specific. | | `a51a018` Fix production sitemap canonical URL | **Ported** | Prevents a mirror from serving sitemap documents generated for a different origin. Ported with OGM hostname coverage. | -| `97c80d2` Add Turnstile bot exemptions and update Kithe bridge | Defer | Mixed BTAA edge policy and Kithe control-plane behavior. Revisit only if those components enter the supported OGM deployment profile. | -| `7becdd8` Version bump for v0.8.4 | Not applicable | BTAA release bookkeeping is not imported. | +| `97c80d2` Add Turnstile bot exemptions and update Kithe bridge | Defer | Mixed upstream edge policy and Kithe control-plane behavior. Revisit only if those components enter the supported OGM deployment profile. | +| `7becdd8` Version bump for v0.8.4 | Not applicable | Upstream release bookkeeping is not imported. | | `caf71d7` Fix similar items missing Elasticsearch docs | **Ported** | Uses `exists` instead of a traced `GET` 404 for retired or intentionally unindexed records. | -| `e6674b4` Bump version to 0.8.5 | Not applicable | BTAA release bookkeeping is not imported. | -| `7165142` Improve telemetry and Elasticsearch visibility controls | Port selectively | Public/suppressed filtering, mapping checks, and cache-key isolation support the mirror public-read contract. AppSignal and BTAA fallback values do not. Requires a dedicated cross-endpoint PR. | -| `1ea6609` Bump version to 0.8.6 | Not applicable | BTAA release bookkeeping is not imported. | +| `e6674b4` Bump version to 0.8.5 | Not applicable | Upstream release bookkeeping is not imported. | +| `7165142` Improve telemetry and Elasticsearch visibility controls | Port selectively | Public/suppressed filtering, mapping checks, and cache-key isolation support the mirror public-read contract. AppSignal and upstream fallback values do not. Requires a dedicated cross-endpoint PR. | +| `1ea6609` Bump version to 0.8.6 | Not applicable | Upstream release bookkeeping is not imported. | | `d60a24a` Fix bridge relationship sync and indexing refresh | Defer | Primarily Kithe Bridge reconciliation. OGM mirrors use GitHub Aardvark harvests as their correctness path. | -| `15c1200` Fix AppSignal frontend telemetry isolation | Not applicable | Frontend telemetry and BTAA observability configuration are not part of this backend product. | +| `15c1200` Fix AppSignal frontend telemetry isolation | Not applicable | Frontend telemetry and upstream observability configuration are not part of this backend product. | | `a477847` Fix Kithe Bridge deletion reconciliation | Defer | Kithe-specific deletion workflow; evaluate against OGM repository removal and tombstone policy separately. | | `9d2eb78` Bump `aiohttp` | Dependency review | Re-resolve against the OGM lockfile and run security/compatibility tests instead of copying one lockfile delta. | | `fb4bbbf` Drop Flower from Kamal deployments | Already satisfied | OGM Kamal roles are `web`, `worker`, and `cron`; Flower remains only an optional local Compose service. | | `a545109` Handle Bridge tombstone deletes | Defer | Useful design input for deletion semantics, but the implementation is coupled to Kithe Bridge. | -| `b8098a0` Bump version to 0.8.7 | Not applicable | BTAA release bookkeeping is not imported. | +| `b8098a0` Bump version to 0.8.7 | Not applicable | Upstream release bookkeeping is not imported. | | `6f4b73b` Refresh project dependencies | Dependency review | Produce a fresh OGM dependency PR with lockfile, license, vulnerability, and runtime verification. | -| `721ea3b` Adjust response handling and file checks | **Ported selectively** | The production-database test now accepts both the legacy `error` and FastAPI `detail` missing-resource envelopes, as required by the full CI suite. Broader Slack, admin, and BTAA response-label changes remain deferred. | -| `93f73d6` Use local Postgres backups in production | Design reference | Mirror backup/rebuild behavior belongs in the OGM operating runbook and secret model, not as a blind BTAA script import. | -| `0a24422` Bump version to 0.8.8 | Not applicable | BTAA release bookkeeping is not imported. | -| `d991db3` Bump version to 0.8.9 | Not applicable | BTAA release bookkeeping is not imported. | +| `721ea3b` Adjust response handling and file checks | **Ported selectively** | The production-database test now accepts both the legacy `error` and FastAPI `detail` missing-resource envelopes, as required by the full CI suite. Broader Slack, admin, and upstream response-label changes remain deferred. | +| `93f73d6` Use local Postgres backups in production | Design reference | Mirror backup/rebuild behavior belongs in the OGM operating runbook and secret model, not as a blind upstream script import. | +| `0a24422` Bump version to 0.8.8 | Not applicable | Upstream release bookkeeping is not imported. | +| `d991db3` Bump version to 0.8.9 | Not applicable | Upstream release bookkeeping is not imported. | | `ab214d5` Fix GEOMG distribution and asset syncing | Port selectively | Aardvark distribution, reference, asset, and relationship correctness is relevant, but the commit is broad and Bridge-heavy. Split it into contract-focused ports after fixture comparison. | | `2d5444f` Fix location filtering for contained resources | **Ported** | Uses document containment for bbox eligibility so a large query retains small fully contained resources. | -| `bfbd4db` Bump version to 0.8.10 | Not applicable | BTAA release bookkeeping is not imported. | +| `bfbd4db` Bump version to 0.8.10 | Not applicable | Upstream release bookkeeping is not imported. | | `ac1b700` Fix relationship cache priming visibility | **Ported** | Routes priming through the public relationship service, connects the legacy database pool for in-process warming, and advances the representation cache version. | | `78466a2` Fix source relationship direction | **Ported** | Emits canonical `dct:isSourceOf`, removes the legacy inverse during sync, canonicalizes old rows, and deduplicates responses. | -| `f1f4092` Release v0.8.11 | Not applicable | Backend changes are release-version updates; the named PMTiles rendering fix is in the BTAA frontend, which this repository does not ship. | +| `f1f4092` Release v0.8.11 | Not applicable | Backend changes are release-version updates; the named PMTiles rendering fix is in the upstream frontend, which this repository does not ship. | ## Port verification diff --git a/legacy/root_api/app/elasticsearch/client.py b/legacy/root_api/app/elasticsearch/client.py index b9eb02c..d45b30e 100644 --- a/legacy/root_api/app/elasticsearch/client.py +++ b/legacy/root_api/app/elasticsearch/client.py @@ -24,7 +24,7 @@ async def init_elasticsearch(): """Initialize Elasticsearch index and mappings.""" from .mappings import INDEX_MAPPING - index_name = os.getenv("ELASTICSEARCH_INDEX", "btaa_ogm_api") + index_name = os.getenv("ELASTICSEARCH_INDEX", "opengeometadata_api") try: # Test the connection diff --git a/legacy/root_api/app/elasticsearch/index.py b/legacy/root_api/app/elasticsearch/index.py index 93c8cfd..8640494 100644 --- a/legacy/root_api/app/elasticsearch/index.py +++ b/legacy/root_api/app/elasticsearch/index.py @@ -18,7 +18,7 @@ async def index_items(): """Index all items from PostgreSQL into Elasticsearch.""" - index_name = os.getenv("ELASTICSEARCH_INDEX", "btaa_ogm_api") + index_name = os.getenv("ELASTICSEARCH_INDEX", "opengeometadata_api") if await es.indices.exists(index=index_name): await es.indices.delete(index=index_name) @@ -278,7 +278,7 @@ async def perform_bulk_indexing(bulk_data, index_name, bulk_size=100): async def reindex_items(): """Reindex all items from PostgreSQL into Elasticsearch with the new mapping.""" - index_name = os.getenv("ELASTICSEARCH_INDEX", "btaa_geometadata_api") + index_name = os.getenv("ELASTICSEARCH_INDEX", "opengeometadata_api") try: # Delete the existing index if it exists diff --git a/legacy/root_api/app/elasticsearch/search.py b/legacy/root_api/app/elasticsearch/search.py index 80af2ec..9d127bb 100644 --- a/legacy/root_api/app/elasticsearch/search.py +++ b/legacy/root_api/app/elasticsearch/search.py @@ -38,7 +38,7 @@ async def search_items( if limit <= 0: limit = 20 # Default to 20 if limit is zero or negative - index_name = os.getenv("ELASTICSEARCH_INDEX", "btaa_ogm_api") + index_name = os.getenv("ELASTICSEARCH_INDEX", "opengeometadata_api") try: # Get the current search criteria diff --git a/legacy/root_api/app/gazetteer/download.py b/legacy/root_api/app/gazetteer/download.py index efdf8f9..cf87cf2 100755 --- a/legacy/root_api/app/gazetteer/download.py +++ b/legacy/root_api/app/gazetteer/download.py @@ -11,7 +11,7 @@ python app/gazetteer/download.py [options] Arguments: - --gazetteer Gazetteer to download (wof, btaa, geonames). Can be specified multiple times. + --gazetteer Gazetteer to download (wof, ogm, geonames). Can be specified multiple times. --download Download and extract data. --export Export data to CSV (for gazetteers that need this step). --all Run all operations for the specified gazetteer(s). diff --git a/legacy/root_api/app/gazetteer/import_all.py b/legacy/root_api/app/gazetteer/import_all.py index e82e711..e5361d0 100644 --- a/legacy/root_api/app/gazetteer/import_all.py +++ b/legacy/root_api/app/gazetteer/import_all.py @@ -5,7 +5,7 @@ This script runs all the gazetteer importers in sequence. - GeoNames: Imports data from tab-delimited .txt files - WOF: Imports data from .csv files -- BTAA: Imports data from .csv files +- OGM: Imports data from .csv files - FAST: Imports data from MARCXML files """ @@ -21,7 +21,7 @@ # Add parent directory to path to import modules sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) -from app.gazetteer.importers.btaa_importer import BtaaImporter +from app.gazetteer.importers.ogm_importer import OgmImporter from app.gazetteer.importers.fast_importer import FastImporter from app.gazetteer.importers.geonames_importer import GeonamesImporter from app.gazetteer.importers.wof_importer import WofImporter @@ -55,7 +55,7 @@ async def import_all( Run all gazetteer importers. Args: - gazetteer_types: List of gazetteer types to import ('geonames', 'wof', 'btaa', 'fast'). + gazetteer_types: List of gazetteer types to import ('geonames', 'wof', 'ogm', 'fast'). If None, all gazetteers will be imported. data_dir: Base directory for gazetteer data. If None, default directories will be used. @@ -67,7 +67,7 @@ async def import_all( # Use all gazetteer types if none specified if not gazetteer_types: - gazetteer_types = ["geonames", "wof", "btaa", "fast"] + gazetteer_types = ["geonames", "wof", "ogm", "fast"] results = {} @@ -90,9 +90,9 @@ async def import_all( logger.info(" - concordances.csv: Concordances to other systems") logger.info(" - geojson.csv: GeoJSON data (if available)") logger.info(" - names.csv: Alternative names") - elif gazetteer_type == "btaa": - importer_dir = os.path.join(data_dir, "btaa") if data_dir else None - importer = BtaaImporter(data_directory=importer_dir) + elif gazetteer_type == "ogm": + importer_dir = os.path.join(data_dir, "ogm") if data_dir else None + importer = OgmImporter(data_directory=importer_dir) elif gazetteer_type == "fast": importer_dir = os.path.join(data_dir, "fast") if data_dir else None importer = FastImporter(data_directory=importer_dir) @@ -150,7 +150,7 @@ def parse_args(): parser.add_argument( "--gazetteers", nargs="+", - choices=["geonames", "wof", "btaa", "fast", "all"], + choices=["geonames", "wof", "ogm", "fast", "all"], default=["all"], help="Gazetteers to import (default: all)", ) @@ -168,7 +168,7 @@ def parse_args(): # Convert 'all' to all gazetteer types gazetteer_types = [] if "all" in args.gazetteers: - gazetteer_types = ["geonames", "wof", "btaa", "fast"] + gazetteer_types = ["geonames", "wof", "ogm", "fast"] else: gazetteer_types = args.gazetteers diff --git a/legacy/root_api/app/gazetteer/importers/__init__.py b/legacy/root_api/app/gazetteer/importers/__init__.py index 5766bee..659c465 100644 --- a/legacy/root_api/app/gazetteer/importers/__init__.py +++ b/legacy/root_api/app/gazetteer/importers/__init__.py @@ -1,14 +1,14 @@ # Gazetteer importers package from .base_importer import BaseImporter -from .btaa_importer import BtaaImporter +from .ogm_importer import OgmImporter from .fast_importer import FastImporter from .geonames_importer import GeonamesImporter from .wof_importer import WofImporter __all__ = [ "BaseImporter", - "BtaaImporter", + "OgmImporter", "FastImporter", "GeonamesImporter", "WofImporter", diff --git a/legacy/root_api/app/gazetteer/importers/btaa_importer.py b/legacy/root_api/app/gazetteer/importers/ogm_importer.py similarity index 92% rename from legacy/root_api/app/gazetteer/importers/btaa_importer.py rename to legacy/root_api/app/gazetteer/importers/ogm_importer.py index 64bcfc1..444e7e4 100644 --- a/legacy/root_api/app/gazetteer/importers/btaa_importer.py +++ b/legacy/root_api/app/gazetteer/importers/ogm_importer.py @@ -4,22 +4,22 @@ from datetime import datetime from typing import Any, Dict -from db.models import gazetteer_btaa +from db.models import gazetteer_ogm from .base_importer import BaseImporter logger = logging.getLogger(__name__) -class BtaaImporter(BaseImporter): - """Importer for BTAA gazetteer data.""" +class OgmImporter(BaseImporter): + """Importer for OGM gazetteer data.""" - # BTAA-specific data directory + # OGM-specific data directory DATA_DIR = os.path.join( os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))), "data", "gazetteers", - "btaa", + "ogm", ) # Map CSV column names to database field names @@ -36,19 +36,19 @@ class BtaaImporter(BaseImporter): } # Smaller chunk size to avoid PostgreSQL parameter limits (similar to GeoNames) - # The BTAA table has 9 fields + 2 for created_at/updated_at, so 11 params per record + # The OGM table has 9 fields + 2 for created_at/updated_at, so 11 params per record # 32767 / 11 ≈ 2979, using 2000 to be safe CHUNK_SIZE = 2000 def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.data_directory = kwargs.get("data_directory") or self.DATA_DIR - self.table = gazetteer_btaa - self.table_name = "gazetteer_btaa" + self.table = gazetteer_ogm + self.table_name = "gazetteer_ogm" def clean_record(self, record: Dict[str, Any]) -> Dict[str, Any]: """ - Clean and transform a BTAA record before insertion. + Clean and transform an OGM record before insertion. Args: record: The raw record from the CSV. @@ -73,7 +73,7 @@ def clean_record(self, record: Dict[str, Any]) -> Dict[str, Any]: async def import_data(self) -> Dict[str, Any]: """ - Import BTAA data from CSV files to the database. + Import OGM data from CSV files to the database. Returns: Dictionary with import statistics. @@ -171,7 +171,7 @@ async def import_data(self) -> Dict[str, Any]: logging.basicConfig(level=logging.INFO) async def run_import(): - importer = BtaaImporter() + importer = OgmImporter() result = await importer.import_data() print(result) diff --git a/legacy/root_api/app/services/search_service.py b/legacy/root_api/app/services/search_service.py index 4ebf495..b7f84d5 100644 --- a/legacy/root_api/app/services/search_service.py +++ b/legacy/root_api/app/services/search_service.py @@ -23,7 +23,7 @@ class SearchService: def __init__(self): - self.index_name = os.getenv("ELASTICSEARCH_INDEX", "btaa_ogm_api") + self.index_name = os.getenv("ELASTICSEARCH_INDEX", "opengeometadata_api") self.es = es async def search( diff --git a/legacy/root_api/db/config.py b/legacy/root_api/db/config.py index 44dcce4..3d9e68d 100644 --- a/legacy/root_api/db/config.py +++ b/legacy/root_api/db/config.py @@ -9,6 +9,6 @@ DB_PASSWORD = os.getenv("POSTGRES_PASSWORD", "postgres") DB_HOST = os.getenv("DB_HOST", "ogm-api-postgres") DB_PORT = os.getenv("DB_PORT", "5432") -DB_NAME = os.getenv("DB_NAME", "btaa_ogm_api") +DB_NAME = os.getenv("DB_NAME", "opengeometadata_api") DATABASE_URL = os.getenv("DATABASE_URL") or f"postgresql+asyncpg://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}" diff --git a/legacy/root_api/db/create_tables.py b/legacy/root_api/db/create_tables.py index 1f5dba5..b33b0a5 100644 --- a/legacy/root_api/db/create_tables.py +++ b/legacy/root_api/db/create_tables.py @@ -10,7 +10,7 @@ # Get the database URL DATABASE_URL = os.getenv( - "DATABASE_URL", "postgresql://postgres:postgres@paradedb:5432/btaa_ogm_api" + "DATABASE_URL", "postgresql://postgres:postgres@paradedb:5432/opengeometadata_api" ) diff --git a/legacy/root_api/db/migrations/create_gazetteer_tables.py b/legacy/root_api/db/migrations/create_gazetteer_tables.py index b431e88..94d79f3 100644 --- a/legacy/root_api/db/migrations/create_gazetteer_tables.py +++ b/legacy/root_api/db/migrations/create_gazetteer_tables.py @@ -160,9 +160,9 @@ def create_gazetteer_tables(): Column("updated_at", Date), ) - # Define the gazetteer_btaa table - gazetteer_btaa = Table( - "gazetteer_btaa", + # Define the gazetteer_ogm table + gazetteer_ogm = Table( + "gazetteer_ogm", metadata, Column("id", Integer, primary_key=True), Column("fast_area", String, nullable=False, index=True), @@ -186,7 +186,7 @@ def create_gazetteer_tables(): ("gazetteer_wof_concordances", gazetteer_wof_concordances), ("gazetteer_wof_geojson", gazetteer_wof_geojson), ("gazetteer_wof_names", gazetteer_wof_names), - ("gazetteer_btaa", gazetteer_btaa), + ("gazetteer_ogm", gazetteer_ogm), ] # Create tables and indexes @@ -199,16 +199,16 @@ def create_gazetteer_tables(): # Create additional indexes with engine.connect() as conn: - # Add compound index for state_abbv and namelsad on gazetteer_btaa - if not inspector.has_index("gazetteer_btaa", "idx_state_abbv_namelsad"): + # Add compound index for state_abbv and namelsad on gazetteer_ogm + if not inspector.has_index("gazetteer_ogm", "idx_state_abbv_namelsad"): conn.execute( text( """ - CREATE INDEX idx_state_abbv_namelsad ON gazetteer_btaa(state_abbv, namelsad); + CREATE INDEX idx_state_abbv_namelsad ON gazetteer_ogm(state_abbv, namelsad); """ ) ) - logger.info("Created compound index on gazetteer_btaa(state_abbv, namelsad)") + logger.info("Created compound index on gazetteer_ogm(state_abbv, namelsad)") # Add additional indexes for optimized querying conn.execute( diff --git a/legacy/root_api/db/models.py b/legacy/root_api/db/models.py index bd376d3..ac619fb 100644 --- a/legacy/root_api/db/models.py +++ b/legacy/root_api/db/models.py @@ -190,9 +190,9 @@ Column("updated_at", TIMESTAMP), ) -# BTAA gazetteer -gazetteer_btaa = Table( - "gazetteer_btaa", +# OGM gazetteer +gazetteer_ogm = Table( + "gazetteer_ogm", metadata, Column("id", Integer, primary_key=True), Column("fast_area", String, nullable=False, index=True), diff --git a/legacy/root_api/ogm/btaa_ogm_api.sql.gz b/legacy/root_api/ogm/ogm_api_legacy.sql.gz similarity index 100% rename from legacy/root_api/ogm/btaa_ogm_api.sql.gz rename to legacy/root_api/ogm/ogm_api_legacy.sql.gz diff --git a/legacy/root_api/btaa_ogm_api.txt b/legacy/root_api/ogm_api_legacy_dump.txt similarity index 100% rename from legacy/root_api/btaa_ogm_api.txt rename to legacy/root_api/ogm_api_legacy_dump.txt diff --git a/legacy/root_api/parade_db_gbl_table.py b/legacy/root_api/parade_db_gbl_table.py index df85e15..ba4817a 100644 --- a/legacy/root_api/parade_db_gbl_table.py +++ b/legacy/root_api/parade_db_gbl_table.py @@ -37,7 +37,7 @@ # Connect to PostgreSQL using environment variables conn = psycopg2.connect( - dbname=os.getenv("POSTGRES_DB", "btaa_ogm_api"), + dbname=os.getenv("POSTGRES_DB", "opengeometadata_api"), user=os.getenv("POSTGRES_USER", "postgres"), password=os.getenv("POSTGRES_PASSWORD", "postgres"), host=os.getenv("POSTGRES_HOST", "paradedb"), # Use the Docker service name diff --git a/legacy/root_api/scripts/process_allmaps.py b/legacy/root_api/scripts/process_allmaps.py index b95d64e..236b9c6 100644 --- a/legacy/root_api/scripts/process_allmaps.py +++ b/legacy/root_api/scripts/process_allmaps.py @@ -19,7 +19,7 @@ sys.path.append(str(Path(__file__).parent)) # Set the correct database URL for local scripts -DATABASE_URL = "postgresql+asyncpg://postgres:postgres@localhost:2345/btaa_ogm_api" +DATABASE_URL = "postgresql+asyncpg://postgres:postgres@localhost:2345/opengeometadata_api" # Setup logging logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s") diff --git a/legacy/root_api/scripts/test_gazetteer_api.py b/legacy/root_api/scripts/test_gazetteer_api.py index 8cce801..0f586e6 100755 --- a/legacy/root_api/scripts/test_gazetteer_api.py +++ b/legacy/root_api/scripts/test_gazetteer_api.py @@ -3,7 +3,7 @@ Test script for gazetteer API endpoints. This script provides a comprehensive test suite for the gazetteer API endpoints. -It tests multiple gazetteer sources (GeoNames, Who's on First, BTAA) and provides +It tests multiple gazetteer sources (GeoNames, Who's on First, OGM) and provides detailed output of the test results. The script can be configured to test different environments by specifying a custom base URL. @@ -36,7 +36,7 @@ def test_endpoints(base_url="http://localhost:8000/api/v1"): 2. Search GeoNames 3. Search Who's on First 4. Get WOF details - 5. Search BTAA + 5. Search OGM 6. Search all gazetteers Args: @@ -113,10 +113,10 @@ def test_endpoints(base_url="http://localhost:8000/api/v1"): else: print("Skipping test 4 as no WOF results were returned in test 3") - # Test 5: Search BTAA - print("\nTest 5: Search BTAA") + # Test 5: Search OGM + print("\nTest 5: Search OGM") print("-" * 80) - response = requests.get(f"{base_url}/gazetteers/btaa", params={"q": "minnesota", "limit": 5}) + response = requests.get(f"{base_url}/gazetteers/ogm", params={"q": "minnesota", "limit": 5}) if response.status_code == 200: data = response.json() results = data.get("data", []) diff --git a/legacy/root_api/tests/README.md b/legacy/root_api/tests/README.md index 0d1d180..c6ff9b1 100644 --- a/legacy/root_api/tests/README.md +++ b/legacy/root_api/tests/README.md @@ -1,6 +1,6 @@ -# Testing the BTAA Geoportal API +# Testing the OpenGeoMetadata API API -This directory contains tests for the BTAA Geoportal API. The tests are organized by component and use pytest as the test runner. +This directory contains tests for the OpenGeoMetadata API API. The tests are organized by component and use pytest as the test runner. ## Test Structure diff --git a/legacy/root_api/tests/api/v1/conftest.py b/legacy/root_api/tests/api/v1/conftest.py index ddad58d..ffa8dca 100644 --- a/legacy/root_api/tests/api/v1/conftest.py +++ b/legacy/root_api/tests/api/v1/conftest.py @@ -11,7 +11,7 @@ # Override DATABASE_URL to use async driver os.environ["DATABASE_URL"] = ( - "postgresql+asyncpg://postgres:postgres@localhost:2345/btaa_ogm_api_test" + "postgresql+asyncpg://postgres:postgres@localhost:2345/ogm_ogm_api_test" ) # Override ELASTICSEARCH_URL to use localhost instead of Docker hostname @@ -21,7 +21,7 @@ from db.database import database # Override the index after app import to ensure it takes effect -os.environ["ELASTICSEARCH_INDEX"] = "btaa_ogm_api" +os.environ["ELASTICSEARCH_INDEX"] = "ogm_ogm_api" @pytest.fixture diff --git a/legacy/root_api/tests/api/v1/test_jsonapi_structure.py b/legacy/root_api/tests/api/v1/test_jsonapi_structure.py index 0add810..05e9640 100644 --- a/legacy/root_api/tests/api/v1/test_jsonapi_structure.py +++ b/legacy/root_api/tests/api/v1/test_jsonapi_structure.py @@ -7,7 +7,7 @@ # Set the database URL for tests os.environ["DATABASE_URL"] = ( - "postgresql+asyncpg://postgres:postgres@localhost:2346/btaa_ogm_api_test" + "postgresql+asyncpg://postgres:postgres@localhost:2346/ogm_ogm_api_test" ) diff --git a/legacy/root_api/tests/api/v1/test_resource_endpoints.py b/legacy/root_api/tests/api/v1/test_resource_endpoints.py index 22a4110..759a500 100644 --- a/legacy/root_api/tests/api/v1/test_resource_endpoints.py +++ b/legacy/root_api/tests/api/v1/test_resource_endpoints.py @@ -7,7 +7,7 @@ # Set the database URL for tests os.environ["DATABASE_URL"] = ( - "postgresql+asyncpg://postgres:postgres@localhost:2346/btaa_ogm_api_test" + "postgresql+asyncpg://postgres:postgres@localhost:2346/ogm_ogm_api_test" ) diff --git a/scripts/sync_backend_from_data_api.sh b/scripts/sync_backend_from_data_api.sh index be39273..80dec97 100755 --- a/scripts/sync_backend_from_data_api.sh +++ b/scripts/sync_backend_from_data_api.sh @@ -2,7 +2,7 @@ set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -CONFIG_FILE="$ROOT_DIR/config/geobtaa-backend-source.env" +CONFIG_FILE="$ROOT_DIR/config/upstream-backend-source.env" OGM_OWNED_PATHS_FILE="$ROOT_DIR/config/ogm-owned-paths.txt" if [[ -f "$CONFIG_FILE" ]]; then @@ -10,11 +10,11 @@ if [[ -f "$CONFIG_FILE" ]]; then source "$CONFIG_FILE" fi -REMOTE_NAME="${GEOBTAA_API_REMOTE_NAME:-upstream}" -REMOTE_URL="${GEOBTAA_API_REMOTE_URL:-https://github.com/geobtaa/api.git}" -UPSTREAM_BRANCH="${GEOBTAA_API_BRANCH:-develop}" -BACKEND_PREFIX="${GEOBTAA_API_BACKEND_PREFIX:-backend}" -SPLIT_BRANCH="${GEOBTAA_API_BACKEND_SPLIT_BRANCH:-vendor/geobtaa-api-backend}" +REMOTE_NAME="${UPSTREAM_API_REMOTE_NAME:-upstream}" +REMOTE_URL="${UPSTREAM_API_REMOTE_URL:-https://github.com/geobtaa/api.git}" +UPSTREAM_BRANCH="${UPSTREAM_API_BRANCH:-develop}" +BACKEND_PREFIX="${UPSTREAM_API_BACKEND_PREFIX:-backend}" +SPLIT_BRANCH="${UPSTREAM_API_BACKEND_SPLIT_BRANCH:-vendor/upstream-api-backend}" DEST_PATH="$ROOT_DIR/$BACKEND_PREFIX" APPLY=0 @@ -266,16 +266,16 @@ cat > "$CONFIG_FILE" <