From 09a30682387c8f9380da35ca4eda7044c1943dbf Mon Sep 17 00:00:00 2001 From: Luis Alberto De La Torre Date: Wed, 11 Nov 2026 12:35:51 -0500 Subject: [PATCH 01/10] chore: ignore Python bytecode caches and IDE folders --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..bef751d253 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +__pycache__/ +*.pyc +node_modules/ +.env +.idea/ From 17ab1963d29a9842dfe20cae9fa795022ab76bfd Mon Sep 17 00:00:00 2001 From: Luis Alberto De La Torre Date: Wed, 11 Nov 2026 12:52:40 -0500 Subject: [PATCH 02/10] fix: install sqlalchemy asyncio extra to include greenlet --- backend/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/requirements.txt b/backend/requirements.txt index 6b777d2aaa..06cb8bd4a6 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -18,7 +18,7 @@ pytz redis>=5.0.0 requests sendgrid -sqlalchemy>=2.0.0 +sqlalchemy[asyncio]>=2.0.0 supabase tenacity>=8.0.0 uvicorn[standard] From 98841be39e0f51364e86084e1989ea57729361f0 Mon Sep 17 00:00:00 2001 From: Luis Alberto De La Torre Date: Wed, 11 Nov 2026 12:52:59 -0500 Subject: [PATCH 03/10] fix: build async engine from DATABASE_URL and fix session context manager --- backend/app/core/database_pool.py | 40 ++++++++++++++++--------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/backend/app/core/database_pool.py b/backend/app/core/database_pool.py index d638dfcfe3..6e49e7231e 100644 --- a/backend/app/core/database_pool.py +++ b/backend/app/core/database_pool.py @@ -1,6 +1,5 @@ import asyncio from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker -from sqlalchemy.pool import QueuePool import logging from ..config import settings @@ -10,42 +9,45 @@ class DatabasePool: def __init__(self): self.engine = None self.session_factory = None - + async def initialize(self): """Initialize database connection pool""" + if self.engine: + return + try: - # Create async engine with connection pooling - database_url = f"postgresql+asyncpg://{settings.supabase_db_user}:{settings.supabase_db_password}@{settings.supabase_db_host}:{settings.supabase_db_port}/{settings.supabase_db_name}" - + database_url = settings.database_url.replace( + "postgresql://", "postgresql+asyncpg://", 1 + ) + self.engine = create_async_engine( database_url, - poolclass=QueuePool, - pool_size=20, # Number of connections to maintain - max_overflow=30, # Additional connections when needed - pool_pre_ping=True, # Validate connections - pool_recycle=3600, # Recycle connections every hour - echo=False # Set to True for SQL debugging + pool_size=20, + max_overflow=30, + pool_pre_ping=True, + pool_recycle=3600, + echo=False ) - + self.session_factory = async_sessionmaker( bind=self.engine, class_=AsyncSession, expire_on_commit=False ) - - logger.info("✅ Database connection pool initialized") - + + logger.info("Database connection pool initialized") + except Exception as e: - logger.error(f"❌ Database pool initialization failed: {e}") + logger.error(f"Database pool initialization failed: {e}") self.engine = None self.session_factory = None - + async def close(self): """Close database connections""" if self.engine: await self.engine.dispose() - - async def get_session(self) -> AsyncSession: + + def get_session(self) -> AsyncSession: """Get database session from pool""" if not self.session_factory: raise Exception("Database pool not initialized") From ca9dc42a580b468b096fa8e229b6c9d2a0deb040 Mon Sep 17 00:00:00 2001 From: Luis Alberto De La Torre Date: Wed, 11 Nov 2026 13:05:49 -0500 Subject: [PATCH 04/10] fix: reuse shared DB pool and remove tenant-agnostic mock fallback --- backend/app/api/v1/dashboard.py | 16 ++-- backend/app/services/reservations.py | 112 ++++++++++----------------- 2 files changed, 52 insertions(+), 76 deletions(-) diff --git a/backend/app/api/v1/dashboard.py b/backend/app/api/v1/dashboard.py index 1ec352d7e3..b1065c0f3c 100644 --- a/backend/app/api/v1/dashboard.py +++ b/backend/app/api/v1/dashboard.py @@ -2,7 +2,9 @@ from typing import Dict, Any from app.services.cache import get_revenue_summary from app.core.auth import authenticate_request as get_current_user +import logging +logger = logging.getLogger(__name__) router = APIRouter() @router.get("/dashboard/summary") @@ -10,13 +12,17 @@ async def get_dashboard_summary( property_id: str, current_user: dict = Depends(get_current_user) ) -> Dict[str, Any]: - + tenant_id = getattr(current_user, "tenant_id", "default_tenant") or "default_tenant" - - revenue_data = await get_revenue_summary(property_id, tenant_id) - + + try: + revenue_data = await get_revenue_summary(property_id, tenant_id) + except Exception: + logger.exception(f"Revenue lookup failed for {property_id} (tenant: {tenant_id})") + raise HTTPException(status_code=503, detail="Revenue data temporarily unavailable") + total_revenue_float = float(revenue_data['total']) - + return { "property_id": revenue_data['property_id'], "total_revenue": total_revenue_float, diff --git a/backend/app/services/reservations.py b/backend/app/services/reservations.py index 384bd00abe..948e79160f 100644 --- a/backend/app/services/reservations.py +++ b/backend/app/services/reservations.py @@ -12,7 +12,7 @@ async def calculate_monthly_revenue(property_id: str, month: int, year: int, db_ end_date = datetime(year, month + 1, 1) else: end_date = datetime(year + 1, 1, 1) - + print(f"DEBUG: Querying revenue for {property_id} from {start_date} to {end_date}") # SQL Simulation (This would be executed against the actual DB) @@ -24,86 +24,56 @@ async def calculate_monthly_revenue(property_id: str, month: int, year: int, db_ AND check_in_date >= $3 AND check_in_date < $4 """ - + # In production this query executes against a database session. # result = await db.fetch_val(query, property_id, tenant_id, start_date, end_date) # return result or Decimal('0') - + return Decimal('0') # Placeholder for now until DB connection is finalized async def calculate_total_revenue(property_id: str, tenant_id: str) -> Dict[str, Any]: """ Aggregates revenue from database. """ - try: - # Import database pool - from app.core.database_pool import DatabasePool - - # Initialize pool if needed - db_pool = DatabasePool() - await db_pool.initialize() - - if db_pool.session_factory: - async with db_pool.get_session() as session: - # Use SQLAlchemy text for raw SQL - from sqlalchemy import text - - query = text(""" - SELECT - property_id, - SUM(total_amount) as total_revenue, - COUNT(*) as reservation_count - FROM reservations - WHERE property_id = :property_id AND tenant_id = :tenant_id - GROUP BY property_id - """) - - result = await session.execute(query, { - "property_id": property_id, - "tenant_id": tenant_id - }) - row = result.fetchone() - - if row: - total_revenue = Decimal(str(row.total_revenue)) - return { - "property_id": property_id, - "tenant_id": tenant_id, - "total": str(total_revenue), - "currency": "USD", - "count": row.reservation_count - } - else: - # No reservations found for this property - return { - "property_id": property_id, - "tenant_id": tenant_id, - "total": "0.00", - "currency": "USD", - "count": 0 - } - else: - raise Exception("Database pool not available") - - except Exception as e: - print(f"Database error for {property_id} (tenant: {tenant_id}): {e}") - - # Create property-specific mock data for testing when DB is unavailable - # This ensures each property shows different figures - mock_data = { - 'prop-001': {'total': '1000.00', 'count': 3}, - 'prop-002': {'total': '4975.50', 'count': 4}, - 'prop-003': {'total': '6100.50', 'count': 2}, - 'prop-004': {'total': '1776.50', 'count': 4}, - 'prop-005': {'total': '3256.00', 'count': 3} - } - - mock_property_data = mock_data.get(property_id, {'total': '0.00', 'count': 0}) - + from sqlalchemy import text + from app.core.database_pool import db_pool + + await db_pool.initialize() + if not db_pool.session_factory: + raise RuntimeError("Database pool not available") + + async with db_pool.get_session() as session: + query = text(""" + SELECT + property_id, + SUM(total_amount) as total_revenue, + COUNT(*) as reservation_count + FROM reservations + WHERE property_id = :property_id AND tenant_id = :tenant_id + GROUP BY property_id + """) + + result = await session.execute(query, { + "property_id": property_id, + "tenant_id": tenant_id + }) + row = result.fetchone() + + if row: + total_revenue = Decimal(str(row.total_revenue)) return { "property_id": property_id, - "tenant_id": tenant_id, - "total": mock_property_data['total'], + "tenant_id": tenant_id, + "total": str(total_revenue), "currency": "USD", - "count": mock_property_data['count'] + "count": row.reservation_count } + + # No reservations found for this property + return { + "property_id": property_id, + "tenant_id": tenant_id, + "total": "0.00", + "currency": "USD", + "count": 0 + } From 882625b63ec9b136315b8daa83fd4b3315c7e91a Mon Sep 17 00:00:00 2001 From: Luis Alberto De La Torre Date: Wed, 11 Nov 2026 13:10:40 -0500 Subject: [PATCH 05/10] fix: scope revenue cache key by tenant to prevent cross-tenant leaks --- backend/app/services/cache.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/backend/app/services/cache.py b/backend/app/services/cache.py index b81474957f..629dc0f869 100644 --- a/backend/app/services/cache.py +++ b/backend/app/services/cache.py @@ -10,20 +10,20 @@ async def get_revenue_summary(property_id: str, tenant_id: str) -> Dict[str, Any """ Fetches revenue summary, utilizing caching to improve performance. """ - cache_key = f"revenue:{property_id}" - + cache_key = f"revenue:{tenant_id}:{property_id}" + # Try to get from cache cached = await redis_client.get(cache_key) if cached: return json.loads(cached) - + # Revenue calculation is delegated to the reservation service. from app.services.reservations import calculate_total_revenue - + # Calculate revenue result = await calculate_total_revenue(property_id, tenant_id) - + # Cache the result for 5 minutes await redis_client.setex(cache_key, 300, json.dumps(result)) - + return result From 6bc099061da9ed44ebf781271cdce8ae8f3dbe64 Mon Sep 17 00:00:00 2001 From: Luis Alberto De La Torre Date: Wed, 11 Nov 2026 13:18:58 -0500 Subject: [PATCH 06/10] fix: round money with Decimal half-up and stop serializing as float --- backend/app/api/v1/dashboard.py | 4 +--- backend/app/services/reservations.py | 6 ++++-- frontend/src/components/RevenueSummary.tsx | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/backend/app/api/v1/dashboard.py b/backend/app/api/v1/dashboard.py index b1065c0f3c..d40ba51ab8 100644 --- a/backend/app/api/v1/dashboard.py +++ b/backend/app/api/v1/dashboard.py @@ -21,11 +21,9 @@ async def get_dashboard_summary( logger.exception(f"Revenue lookup failed for {property_id} (tenant: {tenant_id})") raise HTTPException(status_code=503, detail="Revenue data temporarily unavailable") - total_revenue_float = float(revenue_data['total']) - return { "property_id": revenue_data['property_id'], - "total_revenue": total_revenue_float, + "total_revenue": revenue_data['total'], "currency": revenue_data['currency'], "reservations_count": revenue_data['count'] } diff --git a/backend/app/services/reservations.py b/backend/app/services/reservations.py index 948e79160f..a059a249c3 100644 --- a/backend/app/services/reservations.py +++ b/backend/app/services/reservations.py @@ -1,5 +1,5 @@ from datetime import datetime -from decimal import Decimal +from decimal import Decimal, ROUND_HALF_UP from typing import Dict, Any, List async def calculate_monthly_revenue(property_id: str, month: int, year: int, db_session=None) -> Decimal: @@ -60,7 +60,9 @@ async def calculate_total_revenue(property_id: str, tenant_id: str) -> Dict[str, row = result.fetchone() if row: - total_revenue = Decimal(str(row.total_revenue)) + total_revenue = Decimal(str(row.total_revenue)).quantize( + Decimal("0.01"), rounding=ROUND_HALF_UP + ) return { "property_id": property_id, "tenant_id": tenant_id, diff --git a/frontend/src/components/RevenueSummary.tsx b/frontend/src/components/RevenueSummary.tsx index dbb6d0629d..b14cfa50da 100644 --- a/frontend/src/components/RevenueSummary.tsx +++ b/frontend/src/components/RevenueSummary.tsx @@ -3,14 +3,14 @@ import { SecureAPI } from '../lib/secureApi'; interface RevenueData { property_id: string; - total_revenue: number; + total_revenue: string; currency: string; reservations_count: number; } interface RevenueSummaryProps { propertyId?: string; - debugTenant?: string; + debugTenant?: string; showRaw?: boolean; } @@ -61,7 +61,7 @@ export const RevenueSummary: React.FC = ({ propertyId = 'pr if (error) return
{error}
; if (!data) return null; - const displayTotal = Math.round(data.total_revenue * 100) / 100; + const displayTotal = Number(data.total_revenue); return (
@@ -104,7 +104,7 @@ export const RevenueSummary: React.FC = ({ propertyId = 'pr {/* Precision Warning Area */}
- {Math.abs(data.total_revenue - displayTotal) > 0.000001 && showRaw && ( + {Math.abs(Number(data.total_revenue) - displayTotal) > 0.000001 && showRaw && (
From f51a47da9bc27bb2eb969c6a56ac8436402e24c5 Mon Sep 17 00:00:00 2001 From: Luis Alberto De La Torre Date: Wed, 11 Nov 2026 13:22:33 -0500 Subject: [PATCH 07/10] fix: compute monthly revenue in each property's local timezone --- backend/app/services/reservations.py | 48 +++++++++++++++------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/backend/app/services/reservations.py b/backend/app/services/reservations.py index a059a249c3..3f61c825ae 100644 --- a/backend/app/services/reservations.py +++ b/backend/app/services/reservations.py @@ -1,35 +1,39 @@ -from datetime import datetime from decimal import Decimal, ROUND_HALF_UP from typing import Dict, Any, List -async def calculate_monthly_revenue(property_id: str, month: int, year: int, db_session=None) -> Decimal: +async def calculate_monthly_revenue(property_id: str, tenant_id: str, month: int, year: int) -> Decimal: """ Calculates revenue for a specific month. + Check-ins are bucketed by the property's local timezone, not UTC. """ + from sqlalchemy import text + from app.core.database_pool import db_pool - start_date = datetime(year, month, 1) - if month < 12: - end_date = datetime(year, month + 1, 1) - else: - end_date = datetime(year + 1, 1, 1) - - print(f"DEBUG: Querying revenue for {property_id} from {start_date} to {end_date}") + await db_pool.initialize() + if not db_pool.session_factory: + raise RuntimeError("Database pool not available") - # SQL Simulation (This would be executed against the actual DB) - query = """ - SELECT SUM(total_amount) as total - FROM reservations - WHERE property_id = $1 - AND tenant_id = $2 - AND check_in_date >= $3 - AND check_in_date < $4 - """ + query = text(""" + SELECT COALESCE(SUM(r.total_amount), 0) AS total + FROM reservations r + JOIN properties p + ON p.id = r.property_id AND p.tenant_id = r.tenant_id + WHERE r.property_id = :property_id + AND r.tenant_id = :tenant_id + AND (r.check_in_date AT TIME ZONE p.timezone) >= make_timestamp(:year, :month, 1, 0, 0, 0) + AND (r.check_in_date AT TIME ZONE p.timezone) < make_timestamp(:year, :month, 1, 0, 0, 0) + INTERVAL '1 month' + """) - # In production this query executes against a database session. - # result = await db.fetch_val(query, property_id, tenant_id, start_date, end_date) - # return result or Decimal('0') + async with db_pool.get_session() as session: + result = await session.execute(query, { + "property_id": property_id, + "tenant_id": tenant_id, + "month": month, + "year": year + }) + total = result.scalar_one() - return Decimal('0') # Placeholder for now until DB connection is finalized + return Decimal(str(total)).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP) async def calculate_total_revenue(property_id: str, tenant_id: str) -> Dict[str, Any]: """ From 0faa80717d8271b3a04ab5eae3a73494cbcbf604 Mon Sep 17 00:00:00 2001 From: Luis Alberto De La Torre Date: Wed, 11 Nov 2026 13:26:05 -0500 Subject: [PATCH 08/10] feat: add tenant-scoped properties endpoint --- backend/app/api/v1/dashboard.py | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/backend/app/api/v1/dashboard.py b/backend/app/api/v1/dashboard.py index d40ba51ab8..f641048564 100644 --- a/backend/app/api/v1/dashboard.py +++ b/backend/app/api/v1/dashboard.py @@ -1,5 +1,7 @@ from fastapi import APIRouter, Depends, HTTPException -from typing import Dict, Any +from typing import Dict, Any, List +from sqlalchemy import text +from app.core.database_pool import db_pool from app.services.cache import get_revenue_summary from app.core.auth import authenticate_request as get_current_user import logging @@ -27,3 +29,30 @@ async def get_dashboard_summary( "currency": revenue_data['currency'], "reservations_count": revenue_data['count'] } + +@router.get("/dashboard/properties") +async def get_dashboard_properties( + current_user: dict = Depends(get_current_user) +) -> List[Dict[str, Any]]: + """Returns only the properties that belong to the caller's tenant.""" + tenant_id = getattr(current_user, "tenant_id", None) + if not tenant_id: + raise HTTPException(status_code=403, detail="Tenant could not be resolved") + + await db_pool.initialize() + if not db_pool.session_factory: + raise HTTPException(status_code=503, detail="Properties temporarily unavailable") + + async with db_pool.get_session() as session: + result = await session.execute( + text(""" + SELECT id, name, timezone + FROM properties + WHERE tenant_id = :tenant_id + ORDER BY id + """), + {"tenant_id": tenant_id} + ) + rows = result.fetchall() + + return [{"id": r.id, "name": r.name, "timezone": r.timezone} for r in rows] From 2e53fed99415e7d9a0522cbebe4c126648494eed Mon Sep 17 00:00:00 2001 From: Luis Alberto De La Torre Date: Wed, 11 Nov 2026 13:34:17 -0500 Subject: [PATCH 09/10] fix: load property list from tenant-scoped API instead of hardcoded list --- frontend/src/components/Dashboard.tsx | 35 ++++++++++++++++++--------- frontend/src/lib/secureApi.ts | 14 ++++++++--- 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/frontend/src/components/Dashboard.tsx b/frontend/src/components/Dashboard.tsx index a21bba404e..4bbfd6b601 100644 --- a/frontend/src/components/Dashboard.tsx +++ b/frontend/src/components/Dashboard.tsx @@ -1,16 +1,26 @@ -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import { RevenueSummary } from "./RevenueSummary"; +import { SecureAPI } from "../lib/secureApi"; -const PROPERTIES = [ - { id: 'prop-001', name: 'Beach House Alpha' }, - { id: 'prop-002', name: 'City Apartment Downtown' }, - { id: 'prop-003', name: 'Country Villa Estate' }, - { id: 'prop-004', name: 'Lakeside Cottage' }, - { id: 'prop-005', name: 'Urban Loft Modern' } -]; +interface Property { + id: string; + name: string; + timezone: string; +} const Dashboard: React.FC = () => { - const [selectedProperty, setSelectedProperty] = useState('prop-001'); + const [properties, setProperties] = useState([]); + const [selectedProperty, setSelectedProperty] = useState(''); + const [error, setError] = useState(''); + + useEffect(() => { + SecureAPI.getDashboardProperties() + .then((list) => { + setProperties(list); + if (list.length > 0) setSelectedProperty(list[0].id); + }) + .catch(() => setError('Failed to load properties')); + }, []); return (
@@ -26,7 +36,7 @@ const Dashboard: React.FC = () => { Monthly performance insights for your properties

- + {/* Property Selector */}
@@ -35,7 +45,7 @@ const Dashboard: React.FC = () => { onChange={(e) => setSelectedProperty(e.target.value)} className="block w-full sm:w-auto min-w-[200px] px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 text-sm" > - {PROPERTIES.map((property) => ( + {properties.map((property) => ( @@ -46,7 +56,8 @@ const Dashboard: React.FC = () => {
- + {error &&
{error}
} + {selectedProperty && }
diff --git a/frontend/src/lib/secureApi.ts b/frontend/src/lib/secureApi.ts index f85f04c90a..d85e8d768e 100644 --- a/frontend/src/lib/secureApi.ts +++ b/frontend/src/lib/secureApi.ts @@ -1,11 +1,11 @@ /** * SECURE MULTI-TENANT API CLIENT - * + * * This module provides a centralized, secure API client that ensures ALL database * queries go through the backend with proper tenant isolation. - * + * * Always use this SecureAPI client instead. - * + * * @security This prevents cross-tenant data leakage by enforcing backend-only database access */ @@ -1181,7 +1181,7 @@ export class SecureAPIClient { }); } - // Alias for compatibility + // Alias for compatibility async deleteAppliance(propertyId: string, applianceId: string) { return this.deletePropertyAppliance(propertyId, applianceId); } @@ -1468,6 +1468,12 @@ export class SecureAPIClient { return this.request(`/api/v1/dashboard/summary?${queryParams}`, requestOptions); } + async getDashboardProperties() { + return this.request>( + '/api/v1/dashboard/properties' + ); + } + async uploadCompanyLogo(logo_url: string) { return this.request('/api/v1/company-settings/logo', { method: 'POST', From 5ea3a7d11b6dc6be3a21aaec09750c80c3a331dd Mon Sep 17 00:00:00 2001 From: Luis Alberto De La Torre Date: Wed, 11 Nov 2026 13:41:37 -0500 Subject: [PATCH 10/10] chore(frontend): remove simulated tenant debug header from revenue requests --- frontend/src/components/RevenueSummary.tsx | 13 ++++--------- frontend/src/lib/secureApi.ts | 14 ++++---------- 2 files changed, 8 insertions(+), 19 deletions(-) diff --git a/frontend/src/components/RevenueSummary.tsx b/frontend/src/components/RevenueSummary.tsx index b14cfa50da..91f10d2457 100644 --- a/frontend/src/components/RevenueSummary.tsx +++ b/frontend/src/components/RevenueSummary.tsx @@ -9,26 +9,21 @@ interface RevenueData { } interface RevenueSummaryProps { - propertyId?: string; - debugTenant?: string; + propertyId: string; showRaw?: boolean; } -export const RevenueSummary: React.FC = ({ propertyId = 'prop-001', debugTenant, showRaw }) => { +export const RevenueSummary: React.FC = ({ propertyId, showRaw }) => { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); - const activeTenant = debugTenant || 'candidate'; - useEffect(() => { const fetchRevenue = async () => { setLoading(true); try { - // Use SecureAPI to handle authentication automatically - // We pass the simulatedTenant option which SecureAPI will attach as a header + // Tenant is resolved server-side from the auth token const response = await SecureAPI.getDashboardSummary(propertyId, { - simulatedTenant: activeTenant, timestamp: Date.now() }); setData(response); @@ -41,7 +36,7 @@ export const RevenueSummary: React.FC = ({ propertyId = 'pr }; fetchRevenue(); - }, [propertyId, activeTenant]); + }, [propertyId]); if (loading) { return ( diff --git a/frontend/src/lib/secureApi.ts b/frontend/src/lib/secureApi.ts index d85e8d768e..de8c2efd31 100644 --- a/frontend/src/lib/secureApi.ts +++ b/frontend/src/lib/secureApi.ts @@ -1449,23 +1449,17 @@ export class SecureAPIClient { } // ============= DASHBOARD API ============= + /** - * Get dashboard summary with optional simulation header + * Get dashboard summary. Tenant is resolved server-side from the auth token. */ - async getDashboardSummary(propertyId: string, options?: { simulatedTenant?: string, timestamp?: number }) { + async getDashboardSummary(propertyId: string, options?: { timestamp?: number }) { const queryParams = new URLSearchParams({ property_id: propertyId }); if (options?.timestamp) { queryParams.append('_t', options.timestamp.toString()); } - const requestOptions: RequestInit = {}; - if (options?.simulatedTenant) { - requestOptions.headers = { - 'X-Simulated-Tenant': options.simulatedTenant - }; - } - - return this.request(`/api/v1/dashboard/summary?${queryParams}`, requestOptions); + return this.request(`/api/v1/dashboard/summary?${queryParams}`); } async getDashboardProperties() {