This commit is contained in:
oliver
2026-08-28 10:11:11 -03:00
parent f533ff8e36
commit 585951c8ce
1380 changed files with 1540268 additions and 0 deletions
+472
View File
@@ -0,0 +1,472 @@
"""
Supabase database service for Odoo Directory.
Handles all database operations.
"""
import logging
from typing import Optional, List, Dict, Any
from uuid import UUID
from supabase import create_client, Client
from ..config import get_settings
from ..models.schemas import (
Region,
Country,
City,
Tag,
Company,
CompanyTag,
CityTag,
CountryTag,
ExecutionPlan,
PlanStep,
TagCategory,
StepStatus,
)
logger = logging.getLogger(__name__)
class SupabaseService:
"""Service for Supabase database operations."""
def __init__(self):
"""Initialize Supabase client."""
settings = get_settings()
self.client: Client = create_client(
settings.supabase_url, settings.supabase_key
)
# ==================== Region Operations ====================
def create_region(self, region: Region) -> Region:
"""Create a new region."""
data = {"name": region.name, "slug": region.slug}
result = self.client.table("regions").insert(data).execute()
return Region(**result.data[0])
def get_region_by_slug(self, slug: str) -> Optional[Region]:
"""Get region by slug."""
result = (
self.client.table("regions").select("*").eq("slug", slug).execute()
)
if result.data:
return Region(**result.data[0])
return None
def list_regions(self) -> List[Region]:
"""List all regions."""
result = self.client.table("regions").select("*").execute()
return [Region(**r) for r in result.data]
# ==================== Country Operations ====================
def create_country(self, country: Country) -> Country:
"""Create a new country."""
data = {
"region_id": str(country.region_id) if country.region_id else None,
"name": country.name,
"name_local": country.name_local,
"code": country.code,
"slug": country.slug,
"language": country.language,
}
result = self.client.table("countries").insert(data).execute()
return Country(**result.data[0])
def get_country_by_code(self, code: str) -> Optional[Country]:
"""Get country by ISO code."""
result = (
self.client.table("countries")
.select("*")
.eq("code", code.upper())
.execute()
)
if result.data:
return Country(**result.data[0])
return None
def get_country_by_slug(self, slug: str) -> Optional[Country]:
"""Get country by slug."""
result = (
self.client.table("countries").select("*").eq("slug", slug).execute()
)
if result.data:
return Country(**result.data[0])
return None
def list_countries(self, region_id: Optional[UUID] = None) -> List[Country]:
"""List countries, optionally filtered by region."""
query = self.client.table("countries").select("*")
if region_id:
query = query.eq("region_id", str(region_id))
result = query.execute()
return [Country(**c) for c in result.data]
# ==================== City Operations ====================
def create_city(self, city: City) -> City:
"""Create a new city."""
data = {
"country_id": str(city.country_id) if city.country_id else None,
"name": city.name,
"name_local": city.name_local,
"slug": city.slug,
"state": city.state,
"population": city.population,
"latitude": city.latitude,
"longitude": city.longitude,
}
result = self.client.table("cities").insert(data).execute()
return City(**result.data[0])
def get_city_by_slug(
self, slug: str, country_id: Optional[UUID] = None
) -> Optional[City]:
"""Get city by slug, optionally filtered by country."""
query = self.client.table("cities").select("*").eq("slug", slug)
if country_id:
query = query.eq("country_id", str(country_id))
result = query.execute()
if result.data:
return City(**result.data[0])
return None
def list_cities(self, country_id: Optional[UUID] = None) -> List[City]:
"""List cities, optionally filtered by country."""
query = self.client.table("cities").select("*")
if country_id:
query = query.eq("country_id", str(country_id))
result = query.execute()
return [City(**c) for c in result.data]
# ==================== Tag Operations ====================
def create_tag(self, tag: Tag) -> Tag:
"""Create a new tag."""
data = {
"category": tag.category.value,
"name": tag.name,
"slug": tag.slug,
"description": tag.description,
}
result = self.client.table("tags").insert(data).execute()
return Tag(**result.data[0])
def get_tag_by_slug(self, slug: str) -> Optional[Tag]:
"""Get tag by slug."""
result = self.client.table("tags").select("*").eq("slug", slug).execute()
if result.data:
return Tag(**result.data[0])
return None
def list_tags(self, category: Optional[TagCategory] = None) -> List[Tag]:
"""List tags, optionally filtered by category."""
query = self.client.table("tags").select("*")
if category:
query = query.eq("category", category.value)
result = query.execute()
return [Tag(**t) for t in result.data]
def get_or_create_tag(
self, name: str, category: TagCategory, description: Optional[str] = None
) -> Tag:
"""Get existing tag or create new one."""
slug = name.lower().replace(" ", "-").replace("&", "and")
existing = self.get_tag_by_slug(slug)
if existing:
return existing
return self.create_tag(
Tag(category=category, name=name, slug=slug, description=description)
)
# ==================== Company Operations ====================
def create_company(self, company: Company) -> Company:
"""Create a new company."""
data = {
"city_id": str(company.city_id) if company.city_id else None,
"name": company.name,
"slug": company.slug,
"google_place_id": company.google_place_id,
"address": company.address,
"phone": company.phone,
"website": company.website,
"email": company.email,
"rating": company.rating,
"review_count": company.review_count,
"latitude": company.latitude,
"longitude": company.longitude,
"status": company.status,
}
result = self.client.table("companies").insert(data).execute()
return Company(**result.data[0])
def update_company(self, company_id: UUID, updates: Dict[str, Any]) -> Company:
"""Update a company."""
result = (
self.client.table("companies")
.update(updates)
.eq("id", str(company_id))
.execute()
)
return Company(**result.data[0])
def get_company_by_id(self, company_id: UUID) -> Optional[Company]:
"""Get company by ID."""
result = (
self.client.table("companies")
.select("*")
.eq("id", str(company_id))
.execute()
)
if result.data:
return Company(**result.data[0])
return None
def get_company_by_place_id(self, place_id: str) -> Optional[Company]:
"""Get company by Google Place ID (for deduplication)."""
result = (
self.client.table("companies")
.select("*")
.eq("google_place_id", place_id)
.execute()
)
if result.data:
return Company(**result.data[0])
return None
def list_companies(
self,
city_id: Optional[UUID] = None,
status: Optional[str] = None,
limit: int = 100,
offset: int = 0,
) -> List[Company]:
"""List companies with optional filters."""
query = self.client.table("companies").select("*")
if city_id:
query = query.eq("city_id", str(city_id))
if status:
query = query.eq("status", status)
query = query.range(offset, offset + limit - 1)
result = query.execute()
return [Company(**c) for c in result.data]
def count_companies(
self, city_id: Optional[UUID] = None, status: Optional[str] = None
) -> int:
"""Count companies with optional filters."""
query = self.client.table("companies").select("id", count="exact")
if city_id:
query = query.eq("city_id", str(city_id))
if status:
query = query.eq("status", status)
result = query.execute()
return result.count or 0
# ==================== Company Tag Operations ====================
def add_company_tag(
self, company_id: UUID, tag_id: UUID, confidence: float = 1.0
) -> CompanyTag:
"""Add a tag to a company."""
data = {
"company_id": str(company_id),
"tag_id": str(tag_id),
"confidence": confidence,
}
result = self.client.table("company_tags").insert(data).execute()
return CompanyTag(**result.data[0])
def get_company_tags(self, company_id: UUID) -> List[Tag]:
"""Get all tags for a company."""
result = (
self.client.table("company_tags")
.select("tag_id, tags(*)")
.eq("company_id", str(company_id))
.execute()
)
tags = []
for item in result.data:
if item.get("tags"):
tags.append(Tag(**item["tags"]))
return tags
def remove_company_tags(self, company_id: UUID) -> None:
"""Remove all tags from a company."""
self.client.table("company_tags").delete().eq(
"company_id", str(company_id)
).execute()
# ==================== City Tag Aggregation ====================
def upsert_city_tag(self, city_id: UUID, tag_id: UUID, count: int) -> CityTag:
"""Upsert city tag aggregation."""
data = {
"city_id": str(city_id),
"tag_id": str(tag_id),
"company_count": count,
}
result = (
self.client.table("city_tags")
.upsert(data, on_conflict="city_id,tag_id")
.execute()
)
return CityTag(**result.data[0])
def get_city_tags(self, city_id: UUID) -> List[Dict[str, Any]]:
"""Get all tag aggregations for a city with tag details."""
result = (
self.client.table("city_tags")
.select("*, tags(*)")
.eq("city_id", str(city_id))
.execute()
)
return result.data
# ==================== Country Tag Aggregation ====================
def upsert_country_tag(
self, country_id: UUID, tag_id: UUID, company_count: int, city_count: int
) -> CountryTag:
"""Upsert country tag aggregation."""
data = {
"country_id": str(country_id),
"tag_id": str(tag_id),
"company_count": company_count,
"city_count": city_count,
}
result = (
self.client.table("country_tags")
.upsert(data, on_conflict="country_id,tag_id")
.execute()
)
return CountryTag(**result.data[0])
def get_country_tags(self, country_id: UUID) -> List[Dict[str, Any]]:
"""Get all tag aggregations for a country with tag details."""
result = (
self.client.table("country_tags")
.select("*, tags(*)")
.eq("country_id", str(country_id))
.execute()
)
return result.data
# ==================== Execution Plan Operations ====================
def create_execution_plan(self, plan: ExecutionPlan) -> ExecutionPlan:
"""Create a new execution plan."""
data = {
"name": plan.name,
"country_code": plan.country_code,
"cities": plan.cities,
"status": plan.status.value,
}
result = self.client.table("execution_plans").insert(data).execute()
return ExecutionPlan(**result.data[0])
def update_execution_plan(
self, plan_id: UUID, updates: Dict[str, Any]
) -> ExecutionPlan:
"""Update an execution plan."""
result = (
self.client.table("execution_plans")
.update(updates)
.eq("id", str(plan_id))
.execute()
)
return ExecutionPlan(**result.data[0])
def get_execution_plan(self, plan_id: UUID) -> Optional[ExecutionPlan]:
"""Get execution plan by ID."""
result = (
self.client.table("execution_plans")
.select("*")
.eq("id", str(plan_id))
.execute()
)
if result.data:
plan = ExecutionPlan(**result.data[0])
# Load steps
steps_result = (
self.client.table("plan_steps")
.select("*")
.eq("plan_id", str(plan_id))
.execute()
)
plan.steps = [PlanStep(**s) for s in steps_result.data]
return plan
return None
def list_execution_plans(
self, status: Optional[StepStatus] = None
) -> List[ExecutionPlan]:
"""List execution plans."""
query = self.client.table("execution_plans").select("*")
if status:
query = query.eq("status", status.value)
result = query.execute()
return [ExecutionPlan(**p) for p in result.data]
# ==================== Plan Step Operations ====================
def create_plan_step(self, step: PlanStep) -> PlanStep:
"""Create a new plan step."""
data = {
"plan_id": str(step.plan_id) if step.plan_id else None,
"step_name": step.step_name,
"city_id": str(step.city_id) if step.city_id else None,
"status": step.status.value,
"items_processed": step.items_processed,
"items_total": step.items_total,
}
result = self.client.table("plan_steps").insert(data).execute()
return PlanStep(**result.data[0])
def update_plan_step(self, step_id: UUID, updates: Dict[str, Any]) -> PlanStep:
"""Update a plan step."""
result = (
self.client.table("plan_steps")
.update(updates)
.eq("id", str(step_id))
.execute()
)
return PlanStep(**result.data[0])
def get_plan_steps(
self, plan_id: UUID, step_name: Optional[str] = None
) -> List[PlanStep]:
"""Get steps for a plan."""
query = (
self.client.table("plan_steps").select("*").eq("plan_id", str(plan_id))
)
if step_name:
query = query.eq("step_name", step_name)
result = query.execute()
return [PlanStep(**s) for s in result.data]
# ==================== Error Logging ====================
def log_error(
self,
entity_type: str,
entity_id: Optional[UUID],
operation: str,
error_message: str,
details: Optional[Dict[str, Any]] = None,
) -> None:
"""Log an error to the database."""
data = {
"entity_type": entity_type,
"entity_id": str(entity_id) if entity_id else None,
"operation": operation,
"error_message": error_message,
"details": details or {},
}
try:
self.client.table("error_logs").insert(data).execute()
except Exception as e:
logger.error(f"Failed to log error to database: {e}")