Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions backend/app/api/v1/properties.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from fastapi import APIRouter, Depends
from typing import Dict, Any, List
from sqlalchemy import text
from app.core.auth import authenticate_request as get_current_user
from app.core.database_pool import DatabasePool

router = APIRouter()

@router.get("/properties")
async def list_properties(
current_user: dict = Depends(get_current_user),
) -> List[Dict[str, Any]]:

tenant_id = getattr(current_user, "tenant_id", "default_tenant") or "default_tenant"

db_pool = DatabasePool()
await db_pool.initialize()

async with await db_pool.get_session() as session:
result = await session.execute(
text("SELECT id, name FROM properties WHERE tenant_id = :tenant_id ORDER BY name"),
{"tenant_id": tenant_id}
)
return [{"id": row.id, "name": row.name} for row in result.fetchall()]
4 changes: 1 addition & 3 deletions backend/app/core/database_pool.py
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -15,11 +14,10 @@ async def initialize(self):
"""Initialize database connection pool"""
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
Expand Down
2 changes: 2 additions & 0 deletions backend/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
persistent_auth,
dashboard,
login,
properties,
)

from .monitoring.middleware import PerformanceMonitoringMiddleware
Expand Down Expand Up @@ -183,6 +184,7 @@ async def lifespan(app: FastAPI):

# Dashboard
app.include_router(dashboard.router, prefix="/api/v1", tags=["dashboard"])
app.include_router(properties.router, prefix="/api/v1", tags=["properties"])

# Bootstrap & Settings (for AppContext)
app.include_router(company_settings.router, prefix="/api/v1", tags=["company-settings"])
Expand Down
2 changes: 1 addition & 1 deletion backend/app/services/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ 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)
Expand Down
2 changes: 1 addition & 1 deletion backend/app/services/reservations.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ async def calculate_total_revenue(property_id: str, tenant_id: str) -> Dict[str,
await db_pool.initialize()

if db_pool.session_factory:
async with db_pool.get_session() as session:
async with await db_pool.get_session() as session:
# Use SQLAlchemy text for raw SQL
from sqlalchemy import text

Expand Down
28 changes: 16 additions & 12 deletions frontend/src/components/Dashboard.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import { RevenueSummary } from "./RevenueSummary";

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' }
];
import { SecureAPI } from "../lib/secureApi";

const Dashboard: React.FC = () => {
const [selectedProperty, setSelectedProperty] = useState('prop-001');
const [properties, setProperties] = useState<{ id: string; name: string }[]>([]);
const [selectedProperty, setSelectedProperty] = useState('');

useEffect(() => {
SecureAPI.getAllProperties().then((res) => {
const list = res?.data || [];
setProperties(list);
if (list.length > 0) {
setSelectedProperty(list[0].id);
}
});
}, []);

return (
<div className="p-4 lg:p-6 min-h-full">
Expand All @@ -35,7 +39,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) => (
<option key={property.id} value={property.id}>
{property.name}
</option>
Expand All @@ -46,7 +50,7 @@ const Dashboard: React.FC = () => {
</div>

<div className="space-y-6">
<RevenueSummary propertyId={selectedProperty} />
{selectedProperty && <RevenueSummary propertyId={selectedProperty} />}
</div>
</div>
</div>
Expand Down