mirror of
https://github.com/zvx-echo6/navi.git
synced 2026-08-26 17:31:37 +00:00
New services/navi_landclass/ on :8424 — single blueprint, behavior-identical
port of recon's lib/landclass.py + the /api/landclass handler.
GET /api/landclass?lat=&lon= -> { lat, lon, classifications[], count,
is_public, is_private, summary }; 400 on bad/out-of-range lat/lon.
db.py: faithful port of recon's PostGIS module — lazy module-level
psycopg2.pool.SimpleConnectionPool(minconn=1, maxconn=3) from PADUS_DB_* env;
the ST_Intersects query on pad_units (antimeridian filter, acres-ordered,
limit 10); all PAD-US code->label maps verbatim; graceful degradation
(returns [] when PG is unreachable, never raises/500). Adds reset_pool()
(create_app resets per worker) and probe_db() (SELECT 1) for admin health.
No filesystem state — PostGIS is external. No DB-on-disk migration; only the
5 PADUS_DB_* env vars (PADUS_DB_PASSWORD is a real secret, masked in
admin-info via mask_key; the other 4 shown plain). adds psycopg2-binary>=2.9.
Decision — DROPPED the recon `has_landclass` profile-flag gate: the frontend
already gates on its own has_landclass feature flag, and removing the
cross-service config dependency keeps navi-landclass self-contained per the
"only API" rule (the service's existence is the feature being available).
navi-geo coupling (reverse-bundle needs landclass) — per Phase A, recommend
Option B: navi-geo HTTP-calls /api/landclass and reads `.summary` (the
endpoint already returns it); no shared module. Decided when #6 lands.
Tests (8; recon had 2): point-with-coverage -> classification + decoded
labels, ocean point -> empty, bad/missing/out-of-range lat/lon -> 400, PG
down -> graceful 200 empty (not 500), format_summary unit. Full suite 46.
Deploy: systemd unit (:8424) + nginx snippet (one ^~ /api/landclass block, no
proxy_cache; /api/landclass is public so no Caddy edit — TIER 2 already
routes through nginx since extraction #2).
See ../recon_refactor/extraction-4-phase-a.md (which also corrects the handoff:
/mnt/nav/padus/ is source GIS files, NOT a runtime path — this service has no
/mnt/nav dependency, only PADUS_DB_* + PG network access).
Co-authored-by: Matt Johnson <mj@k7zvx.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
57 lines
2 KiB
Python
57 lines
2 KiB
Python
"""navi-landclass admin-info endpoint (handoff §4.5).
|
|
|
|
``GET /api/admin/navi-landclass/info`` — Authentik-gated, read-only.
|
|
"""
|
|
import os
|
|
import time
|
|
|
|
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 db
|
|
|
|
bp = Blueprint('landclass_admin', __name__)
|
|
|
|
PORT = 8424
|
|
|
|
|
|
def _padus_dependency():
|
|
"""Health-check the PAD-US PostGIS connection via a SELECT 1 probe."""
|
|
start = time.monotonic()
|
|
ok, detail = db.probe_db()
|
|
latency_ms = round((time.monotonic() - start) * 1000, 1)
|
|
result = {'name': 'padus-postgis', 'status': 'ok' if ok else 'error', 'latency_ms': latency_ms}
|
|
if not ok:
|
|
result['error'] = detail
|
|
return result
|
|
|
|
|
|
@bp.route('/api/admin/navi-landclass/info')
|
|
@require_auth
|
|
def navi_landclass_info():
|
|
metrics = current_app.config['METRICS']
|
|
# PADUS_DB_PASSWORD is a real secret -> mask_key. The other four are
|
|
# non-secret connection params, shown as-is.
|
|
info = build_info_response(
|
|
service='navi-landclass',
|
|
version=current_app.config.get('VERSION', 'unknown'),
|
|
port=PORT,
|
|
config={},
|
|
env=[
|
|
{'name': 'PADUS_DB_HOST', 'value': os.environ.get('PADUS_DB_HOST', 'localhost')},
|
|
{'name': 'PADUS_DB_PORT', 'value': os.environ.get('PADUS_DB_PORT', '5432')},
|
|
{'name': 'PADUS_DB_NAME', 'value': os.environ.get('PADUS_DB_NAME', 'padus')},
|
|
{'name': 'PADUS_DB_USER', 'value': os.environ.get('PADUS_DB_USER', 'overture')},
|
|
{'name': 'PADUS_DB_PASSWORD', 'value': mask_key(os.environ.get('PADUS_DB_PASSWORD'))},
|
|
],
|
|
dependencies=[_padus_dependency()],
|
|
filesystem=[], # no FS — PostGIS is external
|
|
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)
|