"""Firma un JWT de static-embedding de Metabase y construye la URL del iframe.""" import time import jwt def sign_metabase_embed_jwt( secret: str, resource_type: str, resource_id: int, base_url: str, params: dict | None = None, exp_seconds: int = 3600, theme: str | None = None, bordered: bool = True, titled: bool = True, ) -> dict: """Firma un JWT de static-embedding de Metabase (HS256) y arma la URL del iframe. Args: secret: secret de embedding de Metabase (Settings > Embedding). NUNCA va al cliente. resource_type: "question" o "dashboard". resource_id: id numerico de la card/dashboard en Metabase. base_url: URL base de la instancia, ej. "https://reports.autingo.es". params: parametros de embedding locked/enabled. Default {}. exp_seconds: TTL del token en segundos desde ahora. Default 3600 (1h). theme: tema opcional ("night" | "transparent" | None). bordered: si el iframe muestra borde. Default True. titled: si el iframe muestra titulo. Default True. Returns: dict con {"token": , "embed_url": , "exp": }. Raises: ValueError: si resource_type no es "question" ni "dashboard". """ if resource_type not in ("question", "dashboard"): raise ValueError( f'resource_type debe ser "question" o "dashboard", recibido: {resource_type!r}' ) exp = int(time.time()) + exp_seconds payload = { "resource": {resource_type: resource_id}, "params": params or {}, "exp": exp, } token = jwt.encode(payload, secret, algorithm="HS256") options = [f"bordered={str(bordered).lower()}", f"titled={str(titled).lower()}"] if theme is not None: options.append(f"theme={theme}") fragment = "&".join(options) embed_url = f"{base_url.rstrip('/')}/embed/{resource_type}/{token}#{fragment}" return {"token": token, "embed_url": embed_url, "exp": exp} if __name__ == "__main__": result = sign_metabase_embed_jwt( secret="0" * 64, resource_type="question", resource_id=8048, base_url="https://reports.autingo.es", ) print(result["embed_url"]) print(f"exp={result['exp']}")