""" Export command for generating static files. """ import json import logging from pathlib import Path from typing import Optional, List, Dict, Any from uuid import UUID import typer from rich.console import Console from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn from jinja2 import Environment, FileSystemLoader from ..services.supabase import SupabaseService from ..services.tags import TagAggregationService from ..services.images import ImageService from ..config import get_settings logger = logging.getLogger(__name__) console = Console() def export_command( country: str, city: Optional[str] = None, output_dir: Optional[str] = None, ) -> None: """ Export static XML/HTML files for a country or city. Generates company pages, city hubs, country hubs, and sitemaps from the database content. Args: country: Country code (e.g., DE) city: Optional specific city slug output_dir: Optional output directory override """ console.print(f"\n[bold blue]Exporting files for {country}[/bold blue]\n") settings = get_settings() db = SupabaseService() tags_service = TagAggregationService(db) image_service = ImageService() # Setup output directory if output_dir: export_path = Path(output_dir) else: export_path = settings.export_dir export_path.mkdir(parents=True, exist_ok=True) # Get country info country_obj = db.get_country_by_code(country.upper()) if not country_obj: console.print(f"[red]Country not found: {country}[/red]") raise typer.Exit(1) # Setup Jinja environment jinja_env = _setup_jinja_environment(settings.templates_dir) # Get cities to export all_cities = db.list_cities(country_id=country_obj.id) if city: cities_to_export = [c for c in all_cities if c.slug == city] if not cities_to_export: console.print(f"[red]City not found: {city}[/red]") raise typer.Exit(1) else: cities_to_export = all_cities console.print(f"Exporting {len(cities_to_export)} cities...") # Export each city total_companies = 0 with Progress( SpinnerColumn(), TextColumn("[progress.description]{task.description}"), BarColumn(), TextColumn("[progress.percentage]{task.percentage:>3.0f}%"), console=console, ) as progress: task = progress.add_task("Exporting...", total=len(cities_to_export)) for city_obj in cities_to_export: city_companies = _export_city( db=db, tags_service=tags_service, image_service=image_service, jinja_env=jinja_env, country=country_obj, city=city_obj, export_path=export_path, ) total_companies += city_companies progress.advance(task) # Export country hub _export_country_hub( db=db, tags_service=tags_service, jinja_env=jinja_env, country=country_obj, cities=cities_to_export, export_path=export_path, ) # Export sitemaps _export_sitemaps( db=db, country=country_obj, cities=cities_to_export, export_path=export_path, ) # Export tag JSON files for frontend filtering _export_tag_json( db=db, tags_service=tags_service, country=country_obj, cities=cities_to_export, export_path=export_path, ) console.print(f"\n[bold green]Export complete![/bold green]") console.print(f" - Companies: {total_companies}") console.print(f" - Cities: {len(cities_to_export)}") console.print(f" - Output: {export_path}") def _setup_jinja_environment(templates_dir: Path) -> Environment: """Setup Jinja2 templating environment.""" # Create templates directory if it doesn't exist templates_dir.mkdir(parents=True, exist_ok=True) # Create default templates if they don't exist _ensure_templates_exist(templates_dir) return Environment( loader=FileSystemLoader(str(templates_dir)), autoescape=True, ) def _ensure_templates_exist(templates_dir: Path) -> None: """Create default templates if they don't exist.""" # Company template company_template = templates_dir / "company.xml" if not company_template.exists(): company_template.write_text(_DEFAULT_COMPANY_TEMPLATE) # City hub template city_template = templates_dir / "city_hub.xml" if not city_template.exists(): city_template.write_text(_DEFAULT_CITY_TEMPLATE) # Country hub template country_template = templates_dir / "country_hub.xml" if not country_template.exists(): country_template.write_text(_DEFAULT_COUNTRY_TEMPLATE) def _export_city( db: SupabaseService, tags_service: TagAggregationService, image_service: ImageService, jinja_env: Environment, country, city, export_path: Path, ) -> int: """Export all files for a city.""" # Create city directory city_path = export_path / country.slug / city.slug city_path.mkdir(parents=True, exist_ok=True) # Get companies companies = db.list_companies( city_id=city.id, status="content_generated", limit=500, ) # Export each company company_template = jinja_env.get_template("company.xml") for company in companies: company_tags = db.get_company_tags(company.id) context = { "company": company, "city": city, "country": country, "tags": company_tags, "services": [t for t in company_tags if hasattr(t, "category") and t.category == "service"], "industries": [t for t in company_tags if hasattr(t, "category") and t.category == "industry"], "modules": [t for t in company_tags if hasattr(t, "category") and t.category == "module"], } content = company_template.render(**context) company_file = city_path / f"{company.slug}.html" company_file.write_text(content, encoding="utf-8") # Export city hub city_tags = tags_service.get_city_tags_by_category(city.id) hub_template = jinja_env.get_template("city_hub.xml") hub_context = { "city": city, "country": country, "companies": companies, "company_count": len(companies), "tags": city_tags, } hub_content = hub_template.render(**hub_context) hub_file = city_path / "index.html" hub_file.write_text(hub_content, encoding="utf-8") return len(companies) def _export_country_hub( db: SupabaseService, tags_service: TagAggregationService, jinja_env: Environment, country, cities: List, export_path: Path, ) -> None: """Export country hub page.""" country_path = export_path / country.slug country_path.mkdir(parents=True, exist_ok=True) # Get aggregate data total_companies = 0 city_data = [] for city in cities: count = db.count_companies(city_id=city.id, status="content_generated") total_companies += count city_data.append({ "city": city, "company_count": count, }) # Sort cities by company count city_data.sort(key=lambda x: x["company_count"], reverse=True) country_tags = tags_service.get_country_tags_by_category(country.id) # Render template template = jinja_env.get_template("country_hub.xml") content = template.render( country=country, cities=city_data, city_count=len(cities), company_count=total_companies, tags=country_tags, ) hub_file = country_path / "index.html" hub_file.write_text(content, encoding="utf-8") def _export_sitemaps( db: SupabaseService, country, cities: List, export_path: Path, ) -> None: """Export XML sitemaps.""" sitemap_path = export_path / "sitemaps" sitemap_path.mkdir(parents=True, exist_ok=True) base_url = "https://odoo-expertos.com" # Company sitemap urls = [] for city in cities: companies = db.list_companies( city_id=city.id, status="content_generated", limit=500, ) for company in companies: urls.append( f"{base_url}/{country.slug}/{city.slug}/{company.slug}.html" ) sitemap_xml = _generate_sitemap_xml(urls) sitemap_file = sitemap_path / f"sitemap-companies-{country.code.lower()}.xml" sitemap_file.write_text(sitemap_xml, encoding="utf-8") # City sitemap city_urls = [ f"{base_url}/{country.slug}/{city.slug}/" for city in cities ] city_sitemap = _generate_sitemap_xml(city_urls) city_sitemap_file = sitemap_path / f"sitemap-cities-{country.code.lower()}.xml" city_sitemap_file.write_text(city_sitemap, encoding="utf-8") def _generate_sitemap_xml(urls: List[str]) -> str: """Generate sitemap XML content.""" lines = [''] lines.append('') for url in urls: lines.append(" ") lines.append(f" {url}") lines.append(" monthly") lines.append(" 0.7") lines.append(" ") lines.append("") return "\n".join(lines) def _export_tag_json( db: SupabaseService, tags_service: TagAggregationService, country, cities: List, export_path: Path, ) -> None: """Export tag aggregations as JSON for frontend filtering.""" json_path = export_path / "tags" json_path.mkdir(parents=True, exist_ok=True) # City tags cities_path = json_path / "cities" cities_path.mkdir(parents=True, exist_ok=True) for city in cities: city_json = tags_service.export_city_tags_json(city.id) json_file = cities_path / f"{city.slug}.json" json_file.write_text(json.dumps(city_json, indent=2), encoding="utf-8") # Country tags country_json = tags_service.export_country_tags_json(country.id) country_file = json_path / f"{country.slug}.json" country_file.write_text(json.dumps(country_json, indent=2), encoding="utf-8") # Default templates _DEFAULT_COMPANY_TEMPLATE = """ {{ company.meta_title or company.name }}

{{ company.name }}

{% if company.description %}

{{ company.description }}

{% endif %}
{% if company.address %}

Address: {{ company.address }}

{% endif %} {% if company.phone %}

Phone: {{ company.phone }}

{% endif %} {% if company.website %}

Website: {{ company.website }}

{% endif %} {% if company.email %}

Email: {{ company.email }}

{% endif %} {% if company.rating %}

Rating: {{ company.rating }}/5 ({{ company.review_count }} reviews)

{% endif %}
{{ company.content_html | safe }}
""" _DEFAULT_CITY_TEMPLATE = """ Odoo Partners in {{ city.name }} | {{ country.name }}

Odoo Partners in {{ city.name }}

Discover {{ company_count }} certified Odoo partners in {{ city.name }}, {{ country.name }}.

Filter by Services

Odoo Partners

{% for company in companies %}

{{ company.name }}

{% if company.description %}

{{ company.description }}

{% endif %} {% if company.rating %}

{{ company.rating }}/5

{% endif %}
{% endfor %}
""" _DEFAULT_COUNTRY_TEMPLATE = """ Odoo Partners in {{ country.name }}

Odoo Partners in {{ country.name }}

Discover {{ company_count }} certified Odoo partners in {{ city_count }} cities across {{ country.name }}.

Cities

Services

"""