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 d4df641..0000000 Binary files a/backend/static/btaa-gin-white.png and /dev/null differ diff --git a/backend/static/btaa-logo-white.png b/backend/static/btaa-logo-white.png deleted file mode 100644 index 063df81..0000000 Binary files a/backend/static/btaa-logo-white.png and /dev/null differ 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" <