2025-06-23 19:43:54 +00:00
|
|
|
from datetime import datetime, timedelta
|
2025-06-22 21:18:52 +00:00
|
|
|
import json
|
2025-06-22 14:00:47 +00:00
|
|
|
import logging
|
|
|
|
|
import logging.config
|
2025-06-11 21:08:11 +00:00
|
|
|
from typing import Annotated
|
2025-06-14 13:39:37 +00:00
|
|
|
from api.auth import get_current_user
|
|
|
|
|
from api.config import DEV_TIER_ORIGINS, PROD_TIER_ORIGINS
|
2025-06-22 21:18:52 +00:00
|
|
|
from dotenv import load_dotenv
|
2025-09-14 19:02:30 +01:00
|
|
|
from fastapi import Depends, FastAPI, Query
|
2025-06-14 13:39:37 +00:00
|
|
|
from api.auth import User
|
2025-06-18 20:38:50 +00:00
|
|
|
from models.listing import QueryParameters
|
2025-07-25 21:32:06 +00:00
|
|
|
from notifications import send_notification
|
2025-06-23 21:09:03 +00:00
|
|
|
from rec import districts
|
2025-07-06 12:02:25 +00:00
|
|
|
from redis_repository import RedisRepository
|
2025-06-11 20:05:26 +00:00
|
|
|
from repositories.listing_repository import ListingRepository
|
|
|
|
|
from database import engine
|
2025-06-14 13:39:37 +00:00
|
|
|
from fastapi.middleware.cors import CORSMiddleware
|
2025-06-22 21:18:52 +00:00
|
|
|
|
|
|
|
|
from tasks import listing_tasks
|
2025-06-15 12:42:56 +00:00
|
|
|
from ui_exporter import export_immoweb
|
2025-10-18 09:58:55 +00:00
|
|
|
from alembic import command
|
|
|
|
|
from alembic.config import Config
|
|
|
|
|
from contextlib import asynccontextmanager
|
|
|
|
|
from celery.exceptions import TaskRevokedError
|
|
|
|
|
from celery_app import app as celery_app
|
|
|
|
|
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
|
|
|
|
|
from api.metrics import metrics_app # Import the Prometheus ASGI app
|
|
|
|
|
from opentelemetry.metrics import get_meter
|
|
|
|
|
|
2025-06-11 20:05:26 +00:00
|
|
|
|
2025-06-22 21:18:52 +00:00
|
|
|
load_dotenv()
|
2025-06-22 14:00:47 +00:00
|
|
|
logger = logging.getLogger("uvicorn")
|
2025-06-21 22:35:32 +00:00
|
|
|
|
2025-06-21 21:52:51 +00:00
|
|
|
|
2025-06-22 14:00:47 +00:00
|
|
|
# @asynccontextmanager
|
|
|
|
|
# async def lifespan(app: FastAPI):
|
|
|
|
|
# alembic_cfg = Config("./alembic.ini")
|
|
|
|
|
# logger.info("Running alembic migrations")
|
|
|
|
|
# command.upgrade(alembic_cfg, "head")
|
|
|
|
|
# logger.info("Finished running alembic migrations")
|
|
|
|
|
# yield
|
|
|
|
|
# logger.warning("Shutting down")
|
2025-06-21 21:52:51 +00:00
|
|
|
|
|
|
|
|
|
2025-06-22 14:00:47 +00:00
|
|
|
# app = FastAPI(lifespan=lifespan)
|
|
|
|
|
app = FastAPI()
|
2025-10-18 09:58:55 +00:00
|
|
|
app.mount("/metrics", metrics_app)
|
|
|
|
|
meter = get_meter(__name__)
|
|
|
|
|
request_counter = meter.create_counter(
|
|
|
|
|
name="custom_request_count",
|
|
|
|
|
description="Number of times /hello was called",
|
|
|
|
|
)
|
|
|
|
|
hist = meter.create_histogram(
|
|
|
|
|
name="custom_request_duration",
|
|
|
|
|
description="Duration of /hello requests in seconds",
|
|
|
|
|
)
|
2025-06-11 21:08:11 +00:00
|
|
|
|
2025-06-21 12:49:04 +00:00
|
|
|
|
2025-06-14 13:39:37 +00:00
|
|
|
# Allow CORS (for React frontend)
|
|
|
|
|
app.add_middleware(
|
|
|
|
|
CORSMiddleware,
|
|
|
|
|
allow_origins=[*DEV_TIER_ORIGINS, *PROD_TIER_ORIGINS],
|
|
|
|
|
allow_methods=["*"],
|
|
|
|
|
allow_headers=["*"],
|
|
|
|
|
)
|
2025-06-11 20:05:26 +00:00
|
|
|
|
|
|
|
|
|
2025-07-26 13:06:28 +00:00
|
|
|
@app.get("/api/status")
|
|
|
|
|
async def get_status():
|
2025-10-18 09:58:55 +00:00
|
|
|
request_counter.add(1, {"method": "GET", "path": "/status"})
|
|
|
|
|
hist.record(1.5, {"method": "GET", "path": "/status"})
|
2025-07-26 13:06:28 +00:00
|
|
|
return {"status": "OK"}
|
|
|
|
|
|
|
|
|
|
|
2025-06-14 15:36:38 +00:00
|
|
|
@app.get("/api/listing")
|
2025-06-14 13:39:37 +00:00
|
|
|
async def get_listing(user: Annotated[User, Depends(get_current_user)]):
|
2025-06-11 20:05:26 +00:00
|
|
|
repository = ListingRepository(engine)
|
2025-06-14 13:39:37 +00:00
|
|
|
listings = await repository.get_listings(limit=5)
|
2025-06-21 22:35:32 +00:00
|
|
|
logger.info(f"Fetched {len(listings)} listings")
|
2025-06-11 20:05:26 +00:00
|
|
|
return {"listings": listings}
|
2025-06-15 12:42:56 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.get("/api/listing_geojson")
|
2025-06-18 20:38:50 +00:00
|
|
|
async def get_listing_geojson(
|
|
|
|
|
user: Annotated[User, Depends(get_current_user)],
|
|
|
|
|
query_parameters: Annotated[QueryParameters, Query()],
|
|
|
|
|
):
|
2025-06-15 12:42:56 +00:00
|
|
|
repository = ListingRepository(engine)
|
2025-06-18 20:38:50 +00:00
|
|
|
geojson_data = await export_immoweb(
|
|
|
|
|
repository, query_parameters=query_parameters, limit=None
|
|
|
|
|
)
|
2025-06-15 12:42:56 +00:00
|
|
|
return geojson_data
|
2025-06-21 12:49:04 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.post("/api/refresh_listings")
|
|
|
|
|
async def refresh_listings(
|
|
|
|
|
user: Annotated[User, Depends(get_current_user)],
|
|
|
|
|
query_parameters: Annotated[QueryParameters, Query()],
|
|
|
|
|
) -> dict[str, str]:
|
2025-07-26 13:06:28 +00:00
|
|
|
await send_notification(
|
|
|
|
|
f"{user.email} refreshing listings with query parameters {query_parameters.model_dump_json()}"
|
|
|
|
|
)
|
2025-07-27 18:33:39 +00:00
|
|
|
# await listing_tasks.async_dump_listings_task(query_parameters.model_dump_json()) # Use this for local debugging - run task in sync
|
|
|
|
|
# return {}
|
2025-06-22 21:18:52 +00:00
|
|
|
# TODO: rate limit
|
2025-06-23 19:43:54 +00:00
|
|
|
expiry_time = datetime.now() + timedelta(minutes=10)
|
|
|
|
|
task = listing_tasks.dump_listings_task.apply_async(
|
|
|
|
|
args=(query_parameters.model_dump_json(),),
|
|
|
|
|
expires=expiry_time,
|
|
|
|
|
)
|
2025-07-06 12:02:25 +00:00
|
|
|
|
|
|
|
|
redis_repository = RedisRepository.instance()
|
|
|
|
|
redis_repository.add_task_for_user(user, task.id)
|
2025-06-22 21:18:52 +00:00
|
|
|
return {"task_id": task.id}
|
2025-06-21 12:49:04 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.get("/api/task_status")
|
|
|
|
|
async def get_task_status(
|
|
|
|
|
user: Annotated[User, Depends(get_current_user)],
|
|
|
|
|
task_id: str,
|
|
|
|
|
) -> dict[str, str]:
|
2025-06-22 21:18:52 +00:00
|
|
|
task_result = listing_tasks.dump_listings_task.AsyncResult(task_id)
|
2025-06-23 19:45:31 +00:00
|
|
|
try:
|
|
|
|
|
result = json.dumps(task_result.result)
|
2025-09-14 19:44:03 +01:00
|
|
|
except Exception:
|
2025-06-23 19:43:54 +00:00
|
|
|
result = str(task_result.result)
|
|
|
|
|
|
2025-06-22 21:18:52 +00:00
|
|
|
return {
|
|
|
|
|
"task_id": task_id,
|
|
|
|
|
"status": task_result.status,
|
2025-07-01 16:12:06 +00:00
|
|
|
"result": result,
|
2025-06-22 21:18:52 +00:00
|
|
|
}
|
2025-06-23 21:09:03 +00:00
|
|
|
|
|
|
|
|
|
2025-07-06 12:02:25 +00:00
|
|
|
@app.get("/api/tasks_for_user")
|
|
|
|
|
async def get_tasks_for_user(
|
|
|
|
|
user: Annotated[User, Depends(get_current_user)],
|
|
|
|
|
) -> list[str]:
|
|
|
|
|
redis_repository = RedisRepository.instance()
|
|
|
|
|
user_tasks = redis_repository.get_tasks_for_user(user)
|
|
|
|
|
return user_tasks
|
|
|
|
|
|
|
|
|
|
|
2025-06-23 21:09:03 +00:00
|
|
|
@app.get("/api/get_districts")
|
2025-07-01 16:12:06 +00:00
|
|
|
async def get_districts(
|
2025-06-23 21:09:03 +00:00
|
|
|
user: Annotated[User, Depends(get_current_user)],
|
|
|
|
|
) -> dict[str, str]:
|
|
|
|
|
return districts.get_districts()
|
2025-10-18 09:58:55 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
FastAPIInstrumentor.instrument_app(app)
|