mirror of
https://github.com/zvx-echo6/navi.git
synced 2026-08-26 17:31:37 +00:00
PR-A of decouple #4-READ. navi-places now reads its own wiki_index.db directly
(NAVI_WIKI_INDEX_DB) instead of HTTP-calling recon's /api/wiki-enrich — same
pattern it already uses for place_cache.db. The 2.1GB DB was copied to
/var/lib/navi-backend/wiki_index.db out-of-band (5,061,763 rows verified).
- NEW services/navi_places/wiki_index.py: verbatim port of recon's
lookup_wiki_index + _get_wiki_index_db, reading NAVI_WIKI_INDEX_DB, mirroring
place_cache.py's db_path()/lazy-conn/reset() pattern. Returns the same
{wiki_summary, wiki_population, wiki_url, wikivoyage_url} shape /api/wiki-enrich
did, so it's a drop-in for the HTTP client.
- place_detail.py: _enrich_with_wiki_via_http -> _enrich_with_wiki_index; call
wiki_index.lookup() instead of wiki_client.enrich_via_recon(); docstrings.
- app.py: wiki_index.reset() per worker/test (alongside place_cache.reset_cache()).
- admin.py: drop the recon-wiki-enrich dependency probe; add NAVI_WIKI_INDEX_DB
env + a read-only filesystem entry. (recon-wiki-rewrite probe kept — separate
decouple.)
- DELETE wiki_client.py (fully replaced).
- test_place.py: convert the wiki test from a monkeypatched HTTP client to a
hermetic tmp wiki_index.db.
Internal localhost migration — no nginx/edge involvement. recon's /api/wiki-enrich
stays live until PR-B (deploy PR-A first so nothing calls the route after removal).
Co-authored-by: Matt Johnson <mj@k7zvx.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
99 lines
3.7 KiB
Python
99 lines
3.7 KiB
Python
"""navi-places admin-info endpoint (handoff §4.5).
|
|
|
|
``GET /api/admin/navi-places/info`` — Authentik-gated, read-only.
|
|
"""
|
|
import os
|
|
import time
|
|
|
|
import requests
|
|
from flask import Blueprint, jsonify, current_app
|
|
|
|
from shared.auth import require_auth
|
|
from shared.admin_info import build_info_response, mask_key
|
|
|
|
from . import overture
|
|
from . import place_cache
|
|
from . import wiki_index
|
|
|
|
bp = Blueprint('places_admin', __name__)
|
|
|
|
PORT = 8425
|
|
|
|
|
|
def _recon_base():
|
|
return os.environ.get('RECON_BASE_URL', 'http://127.0.0.1:8420')
|
|
|
|
|
|
def _overture_dependency():
|
|
start = time.monotonic()
|
|
ok, detail = overture.probe_db()
|
|
latency_ms = round((time.monotonic() - start) * 1000, 1)
|
|
r = {'name': 'overture-postgis', 'status': 'ok' if ok else 'error', 'latency_ms': latency_ms}
|
|
if not ok:
|
|
r['error'] = detail
|
|
return r
|
|
|
|
|
|
def _recon_probe(name, path):
|
|
"""Probe a recon endpoint with no params — expect HTTP 400 ('no usable key'),
|
|
which signals the route exists and responds."""
|
|
start = time.monotonic()
|
|
try:
|
|
resp = requests.get(f"{_recon_base()}{path}", timeout=3)
|
|
latency_ms = round((time.monotonic() - start) * 1000, 1)
|
|
# 400 is the healthy signal (route up, rejects empty input)
|
|
ok = resp.status_code == 400
|
|
r = {'name': name, 'status': 'ok' if ok else 'error', 'latency_ms': latency_ms}
|
|
if not ok:
|
|
r['error'] = f'HTTP {resp.status_code}'
|
|
return r
|
|
except Exception as e:
|
|
latency_ms = round((time.monotonic() - start) * 1000, 1)
|
|
return {'name': name, 'status': 'error', 'latency_ms': latency_ms, 'error': type(e).__name__}
|
|
|
|
|
|
@bp.route('/api/admin/navi-places/info')
|
|
@require_auth
|
|
def navi_places_info():
|
|
metrics = current_app.config['METRICS']
|
|
cache_path = place_cache.db_path()
|
|
wiki_path = wiki_index.db_path()
|
|
# Two real secrets -> mask_key. The rest are non-secret paths/URLs/params.
|
|
info = build_info_response(
|
|
service='navi-places',
|
|
version=current_app.config.get('VERSION', 'unknown'),
|
|
port=PORT,
|
|
config={},
|
|
env=[
|
|
{'name': 'OVERTURE_DB_HOST', 'value': os.environ.get('OVERTURE_DB_HOST', 'localhost')},
|
|
{'name': 'OVERTURE_DB_PORT', 'value': os.environ.get('OVERTURE_DB_PORT', '5432')},
|
|
{'name': 'OVERTURE_DB_NAME', 'value': os.environ.get('OVERTURE_DB_NAME', 'overture')},
|
|
{'name': 'OVERTURE_DB_USER', 'value': os.environ.get('OVERTURE_DB_USER', 'overture')},
|
|
{'name': 'OVERTURE_DB_PASSWORD', 'value': mask_key(os.environ.get('OVERTURE_DB_PASSWORD'))},
|
|
{'name': 'GOOGLE_PLACES_API_KEY', 'value': mask_key(os.environ.get('GOOGLE_PLACES_API_KEY'))},
|
|
{'name': 'RECON_BASE_URL', 'value': _recon_base()},
|
|
{'name': 'NAVI_PLACE_CACHE_DB', 'value': cache_path},
|
|
{'name': 'NAVI_WIKI_INDEX_DB', 'value': wiki_path},
|
|
{'name': 'NAVI_PROFILES_DIR', 'value': os.environ.get('NAVI_PROFILES_DIR', '(default vendored)')},
|
|
],
|
|
dependencies=[
|
|
_overture_dependency(),
|
|
_recon_probe('recon-wiki-rewrite', '/api/wiki-rewrite'),
|
|
],
|
|
filesystem=[{
|
|
'path': cache_path,
|
|
'exists': os.path.exists(cache_path),
|
|
'readable': os.access(cache_path, os.R_OK),
|
|
'writable': os.access(cache_path, os.W_OK),
|
|
}, {
|
|
'path': wiki_path,
|
|
'exists': os.path.exists(wiki_path),
|
|
'readable': os.access(wiki_path, os.R_OK),
|
|
}],
|
|
runtime={
|
|
'uptime_s': round(time.time() - metrics['start_time'], 1),
|
|
'request_count': metrics['request_count'],
|
|
'last_error_at': metrics['last_error_at'],
|
|
},
|
|
)
|
|
return jsonify(info)
|