Coverage for src / idx_api / routers / tours.py: 31%
45 statements
« prev ^ index » next coverage.py v7.13.1, created at 2025-12-28 11:09 -0700
« prev ^ index » next coverage.py v7.13.1, created at 2025-12-28 11:09 -0700
1"""Tour request endpoints."""
3from datetime import datetime, timezone
5from fastapi import APIRouter, Depends, HTTPException
6from sqlalchemy import text
7from sqlalchemy.orm import Session
9from idx_api.database import get_db
10from idx_api.schemas.tour import TourRequestCreate, TourRequestResponse
12router = APIRouter()
15def ensure_tour_requests_table(db: Session) -> None:
16 """Create tour_requests table if it doesn't exist."""
17 # Check if table exists first
18 result = db.execute(
19 text("SELECT name FROM sqlite_master WHERE type='table' AND name='tour_requests'")
20 ).first()
22 if result is None:
23 # Table doesn't exist, create it using raw cursor without commit
24 # DDL executes immediately in SQLite, no commit needed
25 connection = db.connection().connection
26 cursor = connection.cursor()
27 cursor.execute("""
28 CREATE TABLE tour_requests (
29 id INTEGER PRIMARY KEY AUTOINCREMENT,
30 property_id TEXT NOT NULL,
31 property_address TEXT NOT NULL,
32 name TEXT NOT NULL,
33 email TEXT NOT NULL,
34 phone TEXT NOT NULL,
35 preferred_date TEXT,
36 preferred_time TEXT,
37 message TEXT,
38 created_at TEXT NOT NULL,
39 status TEXT NOT NULL DEFAULT 'new',
40 FOREIGN KEY (property_id) REFERENCES properties(listing_id)
41 )
42 """)
43 cursor.close()
44 # No commit - SQLite DDL is immediate, let session handle transaction
47@router.post("", response_model=TourRequestResponse, status_code=201)
48async def create_tour_request(
49 request: TourRequestCreate,
50 db: Session = Depends(get_db),
51):
52 """
53 Create a new tour request for a property.
55 The request will be stored in the database with status 'new'
56 for follow-up by the brokerage.
57 """
58 # Ensure table exists
59 ensure_tour_requests_table(db)
61 # Verify property exists
62 property_exists = db.execute(
63 text("SELECT 1 FROM properties WHERE listing_id = :id AND mlg_can_view = 1"),
64 {"id": request.property_id},
65 ).first()
67 if not property_exists:
68 raise HTTPException(status_code=404, detail="Property not found")
70 # Insert tour request
71 now = datetime.now(timezone.utc).isoformat()
72 result = db.execute(
73 text("""
74 INSERT INTO tour_requests
75 (property_id, property_address, name, email, phone,
76 preferred_date, preferred_time, message, created_at, status)
77 VALUES
78 (:property_id, :property_address, :name, :email, :phone,
79 :preferred_date, :preferred_time, :message, :created_at, 'new')
80 RETURNING *
81 """),
82 {
83 "property_id": request.property_id,
84 "property_address": request.property_address,
85 "name": request.name,
86 "email": request.email,
87 "phone": request.phone,
88 "preferred_date": request.preferred_date,
89 "preferred_time": request.preferred_time,
90 "message": request.message,
91 "created_at": now,
92 },
93 )
95 # Fetch result BEFORE committing to avoid "SQL statements in progress"
96 tour_request = result.mappings().first()
97 if not tour_request:
98 raise HTTPException(status_code=500, detail="Failed to create tour request")
100 # Now commit after consuming the result
101 db.commit()
103 return TourRequestResponse.model_validate(dict(tour_request))
106@router.get("", response_model=list[TourRequestResponse])
107async def list_tour_requests(
108 status: str | None = None,
109 limit: int = 50,
110 db: Session = Depends(get_db),
111):
112 """
113 List tour requests (admin endpoint).
115 Can filter by status: new, contacted, scheduled, completed, cancelled
116 """
117 ensure_tour_requests_table(db)
119 conditions = []
120 params = {"limit": limit}
122 if status:
123 conditions.append("status = :status")
124 params["status"] = status
126 where_clause = f"WHERE {' AND '.join(conditions)}" if conditions else ""
128 results = db.execute(
129 text(f"""
130 SELECT * FROM tour_requests
131 {where_clause}
132 ORDER BY created_at DESC
133 LIMIT :limit
134 """),
135 params,
136 ).mappings().all()
138 return [TourRequestResponse.model_validate(dict(r)) for r in results]
141@router.get("/{request_id}", response_model=TourRequestResponse)
142async def get_tour_request(
143 request_id: int,
144 db: Session = Depends(get_db),
145):
146 """Get a specific tour request by ID."""
147 ensure_tour_requests_table(db)
149 result = db.execute(
150 text("SELECT * FROM tour_requests WHERE id = :id"),
151 {"id": request_id},
152 ).mappings().first()
154 if not result:
155 raise HTTPException(status_code=404, detail="Tour request not found")
157 return TourRequestResponse.model_validate(dict(result))