mapApp / services /supabase_service.py
mtahan's picture
Initial backend commit
a31fd7f verified
Raw
History Blame Contribute Delete
9.17 kB
import logging
import uuid
from typing import List, Dict, Any, Optional
from supabase import create_client, Client
from config import settings
logger = logging.getLogger("supabase_service")
# Try to initialize Supabase client
supabase_client: Optional[Client] = None
if settings.SUPABASE_URL and settings.SUPABASE_KEY:
try:
supabase_client = create_client(settings.SUPABASE_URL, settings.SUPABASE_KEY)
logger.info("Supabase client initialized successfully.")
except Exception as e:
logger.error(f"Error initializing Supabase client: {e}")
else:
logger.warning("Supabase URL or Key not set. Using mock in-memory database.")
# --- IN-MEMORY MOCK DATABASE ---
# Pre-populated with some mock locations in Guarenas-Guatire
MOCK_SHELTERS = [
{
"id": "s1",
"name": "Refugio Polideportivo Los Naranjos",
"capacity": 150,
"occupancy": 45,
"latitude": 10.4678,
"longitude": -66.6210,
"resources": {"food_boxes": 200, "medical_kits": 50, "water_liters": 1000}
},
{
"id": "s2",
"name": "Refugio U.E. Antonio José de Sucre",
"capacity": 100,
"occupancy": 80,
"latitude": 10.4585,
"longitude": -66.6025,
"resources": {"food_boxes": 80, "medical_kits": 20, "water_liters": 400}
},
{
"id": "s3",
"name": "Refugio Centro Cultural Guatire",
"capacity": 200,
"occupancy": 110,
"latitude": 10.4722,
"longitude": -66.5412,
"resources": {"food_boxes": 300, "medical_kits": 100, "water_liters": 2500}
}
]
MOCK_DAMAGE_REPORTS = [
{
"id": "d1",
"severity": "high",
"latitude": 10.4621,
"longitude": -66.6185,
"photo_url": "https://images.unsplash.com/photo-1594897030264-ab7d87efc473?w=500",
"status": "pending",
"details": {
"owner_name": "Juan Perez",
"owner_contact": "+58 412 1234567",
"exact_address": "Sector Oropeza Castillo, Calle 3, Casa Nro 12, Guarenas",
"description": "Fallas estructurales graves en vigas principales y grietas en paredes de carga."
}
},
{
"id": "d2",
"severity": "collapsed",
"latitude": 10.4690,
"longitude": -66.6102,
"photo_url": "https://images.unsplash.com/photo-1580618672591-eb180b1a973f?w=500",
"status": "verified",
"details": {
"owner_name": "Maria Rodriguez",
"owner_contact": "+58 414 7654321",
"exact_address": "Urbanización Vicente Emilio Sojo, Bloque 4, Guarenas",
"description": "Derrumbe parcial del segundo piso de la vivienda unifamiliar."
}
}
]
# --- SERVICE INTERFACE FUNCTIONS ---
def save_damage_report(
severity: str,
latitude: float,
longitude: float,
photo_url: Optional[str],
status: str,
owner_name: str,
owner_contact: str,
exact_address: str,
description: Optional[str]
) -> Dict[str, Any]:
"""
Saves a damage report. Connects to Supabase if available, otherwise writes to in-memory store.
"""
report_id = str(uuid.uuid4())
if supabase_client:
try:
# PostGIS expects geometry as 'POINT(lng lat)' -> WGS84 EPSG:4326
point_wkt = f"POINT({longitude} {latitude})"
# 1. Insert into public damage_reports
report_data = {
"id": report_id,
"severity": severity,
"location": point_wkt,
"photo_url": photo_url,
"status": status
}
res_report = supabase_client.table("damage_reports").insert(report_data).execute()
# 2. Insert private details
details_data = {
"id": report_id,
"owner_name": owner_name,
"owner_contact": owner_contact,
"exact_address": exact_address,
"description": description
}
res_details = supabase_client.table("damage_details").insert(details_data).execute()
return {
"id": report_id,
"severity": severity,
"latitude": latitude,
"longitude": longitude,
"photo_url": photo_url,
"status": status,
"details": details_data
}
except Exception as e:
logger.error(f"Error saving to Supabase: {e}. Falling back to in-memory storage.")
# Fallback to in-memory if DB fails during emergency
# In-memory save
new_report = {
"id": report_id,
"severity": severity,
"latitude": latitude,
"longitude": longitude,
"photo_url": photo_url,
"status": status,
"details": {
"owner_name": owner_name,
"owner_contact": owner_contact,
"exact_address": exact_address,
"description": description
}
}
MOCK_DAMAGE_REPORTS.append(new_report)
logger.info(f"Saved damage report to in-memory store: {report_id}")
return new_report
def get_damage_reports_in_bbox(
min_lat: float, min_lng: float, max_lat: float, max_lng: float
) -> List[Dict[str, Any]]:
"""
Retrieves public damage reports within a bounding box.
"""
if supabase_client:
try:
# We can run an RPC or raw spatial query in Supabase.
# PostgREST filter syntax: ST_Contains bounding box
# To simplify, we can call an RPC function defined in our database,
# or do a bounding box query using PostgREST filters if available,
# or query all and filter in Python for small datasets, or write custom RPC.
# Let's use RPC 'get_damage_in_bbox' or query with PostgREST raw select.
# PostgREST allows filtering on geometry.
# For robustness, we will fetch and parse, or fall back if query syntax fails.
# Let's request coordinates as lat/lng from PostGIS:
# select('id, severity, photo_url, status, location')
# By default PostGIS geometry returns GeoJSON or WKB.
res = supabase_client.rpc(
"get_damage_in_bbox",
{"min_lat": min_lat, "min_lng": min_lng, "max_lat": max_lat, "max_lng": max_lng}
).execute()
return res.data
except Exception as e:
logger.error(f"Supabase spatial query failed: {e}. Filtering in-memory.")
# In-memory bounding box filtering
filtered = []
for r in MOCK_DAMAGE_REPORTS:
lat, lng = r["latitude"], r["longitude"]
if min_lat <= lat <= max_lat and min_lng <= lng <= max_lng:
# Strip private details unless authenticated
# Return only public data for public map
filtered.append({
"id": r["id"],
"severity": r["severity"],
"latitude": lat,
"longitude": lng,
"photo_url": r["photo_url"],
"status": r["status"]
})
return filtered
def get_shelters_in_bbox(
min_lat: float, min_lng: float, max_lat: float, max_lng: float
) -> List[Dict[str, Any]]:
"""
Retrieves shelters within a bounding box.
"""
if supabase_client:
try:
res = supabase_client.rpc(
"get_shelters_in_bbox",
{"min_lat": min_lat, "min_lng": min_lng, "max_lat": max_lat, "max_lng": max_lng}
).execute()
return res.data
except Exception as e:
logger.error(f"Supabase spatial query for shelters failed: {e}. Filtering in-memory.")
# In-memory bounding box filtering
filtered = []
for s in MOCK_SHELTERS:
lat, lng = s["latitude"], s["longitude"]
if min_lat <= lat <= max_lat and min_lng <= lng <= max_lng:
filtered.append(s)
return filtered
def save_shelter(
name: str, capacity: int, occupancy: int, latitude: float, longitude: float, resources: Dict[str, Any]
) -> Dict[str, Any]:
"""
Saves a new shelter.
"""
shelter_id = str(uuid.uuid4())
if supabase_client:
try:
point_wkt = f"POINT({longitude} {latitude})"
shelter_data = {
"id": shelter_id,
"name": name,
"capacity": capacity,
"occupancy": occupancy,
"location": point_wkt,
"resources": resources
}
supabase_client.table("shelters").insert(shelter_data).execute()
return shelter_data
except Exception as e:
logger.error(f"Error saving shelter to Supabase: {e}")
new_shelter = {
"id": shelter_id,
"name": name,
"capacity": capacity,
"occupancy": occupancy,
"latitude": latitude,
"longitude": longitude,
"resources": resources
}
MOCK_SHELTERS.append(new_shelter)
return new_shelter