sass-imobiliaria/backend/app/routes/locations.py

155 lines
5.2 KiB
Python

import unicodedata
import re
from flask import Blueprint, jsonify, request
from sqlalchemy import func
from app.extensions import db
from app.models.location import City, Neighborhood
from app.models.property import Property
from app.schemas.catalog import CityOut, NeighborhoodOut
locations_bp = Blueprint("locations", __name__, url_prefix="/api/v1")
def _slugify(text: str) -> str:
text = unicodedata.normalize("NFD", text)
text = "".join(c for c in text if unicodedata.category(c) != "Mn")
text = text.lower()
text = re.sub(r"[^a-z0-9\s-]", "", text)
text = re.sub(r"[\s/]+", "-", text.strip())
return text
# ── Public endpoints ──────────────────────────────────────────────────────────
@locations_bp.get("/cities")
def list_cities():
"""List all cities ordered by state + name, with property_count."""
rows = (
db.session.query(City, func.count(Property.id).label("cnt"))
.outerjoin(
Property,
(Property.city_id == City.id) & (Property.is_active.is_(True)),
)
.group_by(City.id)
.order_by(City.state, City.name)
.all()
)
return jsonify(
[
{**CityOut.model_validate(city).model_dump(), "property_count": cnt}
for city, cnt in rows
]
)
@locations_bp.get("/neighborhoods")
def list_neighborhoods():
"""List neighborhoods, optionally filtered by city_id, with property_count."""
city_id = request.args.get("city_id")
q = (
db.session.query(Neighborhood, func.count(Property.id).label("cnt"))
.outerjoin(
Property,
(Property.neighborhood_id == Neighborhood.id)
& (Property.is_active.is_(True)),
)
.group_by(Neighborhood.id)
)
if city_id:
try:
q = q.filter(Neighborhood.city_id == int(city_id))
except ValueError:
pass
rows = q.order_by(Neighborhood.name).all()
return jsonify(
[
{
**NeighborhoodOut.model_validate(nbh).model_dump(),
"property_count": cnt,
}
for nbh, cnt in rows
]
)
# ── Admin endpoints ───────────────────────────────────────────────────────────
@locations_bp.post("/admin/cities")
def create_city():
data = request.get_json(force=True) or {}
name = (data.get("name") or "").strip()
state = (data.get("state") or "").strip().upper()[:2]
if not name or not state:
return jsonify({"error": "name e state são obrigatórios"}), 400
slug = data.get("slug") or _slugify(name)
if City.query.filter_by(slug=slug).first():
return jsonify({"error": "Já existe uma cidade com esse slug"}), 409
city = City(name=name, slug=slug, state=state)
db.session.add(city)
db.session.commit()
return jsonify(CityOut.model_validate(city).model_dump()), 201
@locations_bp.put("/admin/cities/<int:city_id>")
def update_city(city_id: int):
city = City.query.get_or_404(city_id)
data = request.get_json(force=True) or {}
if "name" in data:
city.name = data["name"].strip()
if "state" in data:
city.state = data["state"].strip().upper()[:2]
if "slug" in data:
city.slug = data["slug"].strip()
db.session.commit()
return jsonify(CityOut.model_validate(city).model_dump())
@locations_bp.delete("/admin/cities/<int:city_id>")
def delete_city(city_id: int):
city = City.query.get_or_404(city_id)
db.session.delete(city)
db.session.commit()
return "", 204
@locations_bp.post("/admin/neighborhoods")
def create_neighborhood():
data = request.get_json(force=True) or {}
name = (data.get("name") or "").strip()
city_id = data.get("city_id")
if not name or not city_id:
return jsonify({"error": "name e city_id são obrigatórios"}), 400
city = City.query.get_or_404(int(city_id))
slug = data.get("slug") or _slugify(name)
if Neighborhood.query.filter_by(slug=slug, city_id=city.id).first():
return jsonify({"error": "Já existe um bairro com esse slug nessa cidade"}), 409
neighborhood = Neighborhood(name=name, slug=slug, city_id=city.id)
db.session.add(neighborhood)
db.session.commit()
return jsonify(NeighborhoodOut.model_validate(neighborhood).model_dump()), 201
@locations_bp.put("/admin/neighborhoods/<int:neighborhood_id>")
def update_neighborhood(neighborhood_id: int):
n = Neighborhood.query.get_or_404(neighborhood_id)
data = request.get_json(force=True) or {}
if "name" in data:
n.name = data["name"].strip()
if "slug" in data:
n.slug = data["slug"].strip()
if "city_id" in data:
n.city_id = int(data["city_id"])
db.session.commit()
return jsonify(NeighborhoodOut.model_validate(n).model_dump())
@locations_bp.delete("/admin/neighborhoods/<int:neighborhood_id>")
def delete_neighborhood(neighborhood_id: int):
n = Neighborhood.query.get_or_404(neighborhood_id)
db.session.delete(n)
db.session.commit()
return "", 204