diff --git a/server.py b/server.py index 066d950..113d3e9 100644 --- a/server.py +++ b/server.py @@ -856,6 +856,14 @@ def create_asset(body: AssetCreate): if body.assigned_to is not None: _validate_ref(conn, "users", body.assigned_to, "User") + # Auto-populate address from GPS coordinates + if body.latitude is not None and body.longitude is not None and not (body.address or "").strip(): + geo = reverse_geocode(body.latitude, body.longitude) + if geo: + body.address = geo.get("address", "") + body.building_name = geo.get("building_name", body.building_name or "") + body.building_number = geo.get("building_number", body.building_number or "") + columns, values = _build_asset_insert(body, machine_id, name) placeholders = ", ".join(["?"] * len(columns)) col_names = ", ".join(columns) @@ -1115,6 +1123,16 @@ def update_asset(asset_id: int, body: AssetUpdate): if body.geofence_radius_meters is not None: updates["geofence_radius_meters"] = body.geofence_radius_meters + # Auto-populate address from GPS when lat/lng are provided + lat = updates.get("latitude") if "latitude" in updates else None + lng = updates.get("longitude") if "longitude" in updates else None + if lat is not None and lng is not None: + geo = reverse_geocode(lat, lng) + if geo: + updates["address"] = geo.get("address", "") + updates["building_name"] = geo.get("building_name", "") + updates["building_number"] = geo.get("building_number", "") + if updates: updates["updated_at"] = "datetime('now')" set_clause = ", ".join( @@ -1174,12 +1192,18 @@ def create_checkin(body: CheckinCreate): conn.commit() checkin_id = cursor.lastrowid - # Auto-update asset's last known location + # Auto-update asset's last known location and address if body.latitude is not None and body.longitude is not None: conn.execute( "UPDATE assets SET latitude = ?, longitude = ? WHERE id = ?", (body.latitude, body.longitude, body.asset_id), ) + geo = reverse_geocode(body.latitude, body.longitude) + if geo: + conn.execute( + "UPDATE assets SET address = ?, building_name = ?, building_number = ? WHERE id = ?", + (geo.get("address", ""), geo.get("building_name", ""), geo.get("building_number", ""), body.asset_id), + ) conn.commit() # Auto-log visit @@ -2665,22 +2689,22 @@ async def ocr_sticker(file: UploadFile = File(...)): # ─── Reverse Geocode (Nominatim) ──────────────────────────────────────────── -@app.get("/api/geocode") -def geocode(lat: float = Query(...), lng: float = Query(...)): - """Reverse geocode GPS coordinates to address using OpenStreetMap Nominatim.""" - import urllib.request - import urllib.error +def reverse_geocode(lat: float, lng: float) -> dict | None: + """Call OpenStreetMap Nominatim to reverse geocode GPS coords. + + Returns a dict with address fields mapped to our schema, or None on failure. + Does NOT raise HTTPException — callers handle the None case. + """ try: url = ( f"https://nominatim.openstreetmap.org/reverse" f"?format=json&lat={lat}&lon={lng}&zoom=18&addressdetails=1" ) - # Set a proper User-Agent as required by Nominatim ToS req = urllib.request.Request(url, headers={"User-Agent": "CanteenAssetTracker/2.0"}) with urllib.request.urlopen(req, timeout=5) as resp: data = _json.loads(resp.read()) - except (urllib.error.URLError, _json.JSONDecodeError, OSError) as e: - raise HTTPException(status_code=502, detail=f"Geocoding failed: {str(e)}") + except (urllib.error.URLError, _json.JSONDecodeError, OSError): + return None addr = data.get("address", {}) display_name = data.get("display_name", "") @@ -2717,10 +2741,19 @@ def geocode(lat: float = Query(...), lng: float = Query(...)): "city": city, "state": state, "postcode": postcode, - "address": formatted, # mapped to our asset.address field + "address": formatted, } +@app.get("/api/geocode") +def geocode(lat: float = Query(...), lng: float = Query(...)): + """Reverse geocode GPS coordinates to address using OpenStreetMap Nominatim.""" + result = reverse_geocode(lat, lng) + if result is None: + raise HTTPException(status_code=502, detail="Geocoding failed: unable to reach Nominatim") + return result + + # ─── Static Files (mounted last to not shadow routes) ────────────────────── app.mount("/uploads", StaticFiles(directory=str(UPLOADS_DIR)), name="uploads") diff --git a/static/index.html b/static/index.html index da57006..9d5fb18 100644 --- a/static/index.html +++ b/static/index.html @@ -3518,28 +3518,6 @@ }).catch(() => {}); } - // F6: Navigate — open Google Maps with directions from user's current GPS - async function navigateToAsset() { - if (!AppState.currentAssetId) return; - if (!AppState.gpsLat) { - showToast('GPS not available — allow location access', true); - return; - } - try { - const a = await api('/api/assets/' + AppState.currentAssetId); - if (!a.latitude || !a.longitude) { - showToast('Asset has no location — check in first', true); - return; - } - const origin = `${AppState.gpsLat},${AppState.gpsLng}`; - const dest = `${a.latitude},${a.longitude}`; - const url = `https://www.google.com/maps/dir/?api=1&origin=${origin}&destination=${dest}&travelmode=driving`; - window.open(url, '_blank'); - } catch (e) { - showToast('Navigation error: ' + e.message, true); - } - } - async function navigateToAsset() { if (!AppState.currentAssetId) return; if (!AppState.gpsLat || !AppState.gpsLng) {