feat: add full project - backend, frontend, docker, specs and configs

This commit is contained in:
MatheusAlves96 2026-04-20 23:59:45 -03:00
parent b77c7d5a01
commit e6cb06255b
24489 changed files with 61341 additions and 36 deletions

View file

@ -0,0 +1,5 @@
# Importação explícita dos modelos para registro no metadata
from .saved_property import SavedProperty
from .visit_request import VisitRequest
from .boleto import Boleto
from .page_view import PageView

View file

@ -0,0 +1,25 @@
from app.extensions import db
class Agent(db.Model):
__tablename__ = "agents"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(200), nullable=False)
photo_url = db.Column(db.String(512), nullable=True)
creci = db.Column(db.String(50), nullable=False)
email = db.Column(db.String(200), nullable=False)
phone = db.Column(db.String(30), nullable=False)
bio = db.Column(db.Text, nullable=True)
is_active = db.Column(db.Boolean, nullable=False, default=True)
display_order = db.Column(db.Integer, nullable=False, default=0)
created_at = db.Column(db.DateTime, nullable=False, server_default=db.func.now())
updated_at = db.Column(
db.DateTime,
nullable=False,
server_default=db.func.now(),
onupdate=db.func.now(),
)
def __repr__(self) -> str:
return f"<Agent {self.name!r}>"

View file

@ -0,0 +1,30 @@
import uuid
from datetime import datetime
from app.extensions import db
class Boleto(db.Model):
__tablename__ = "boletos"
id = db.Column(db.String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
user_id = db.Column(
db.String(36),
db.ForeignKey("client_users.id", ondelete="SET NULL"),
nullable=True,
index=True,
)
property_id = db.Column(
db.UUID(as_uuid=True),
db.ForeignKey("properties.id", ondelete="SET NULL"),
nullable=True,
index=True,
)
description = db.Column(db.String(200), nullable=False)
amount = db.Column(db.Numeric(12, 2), nullable=False)
due_date = db.Column(db.Date, nullable=False)
status = db.Column(db.String(20), nullable=False, default="pending")
url = db.Column(db.String(500), nullable=True)
created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
user = db.relationship("ClientUser", backref=db.backref("boletos", lazy="select"))
property = db.relationship("Property", foreign_keys=[property_id], lazy="joined")

View file

@ -0,0 +1,67 @@
import uuid
from app.extensions import db
# Association table N:N between Property and Amenity — no model class needed
property_amenity = db.Table(
"property_amenity",
db.Column(
"property_id",
db.UUID(as_uuid=True),
db.ForeignKey("properties.id", ondelete="CASCADE"),
primary_key=True,
),
db.Column(
"amenity_id",
db.Integer,
db.ForeignKey("amenities.id", ondelete="CASCADE"),
primary_key=True,
),
)
class PropertyType(db.Model):
"""Hierarchical property type: Residencial > Apartamento"""
__tablename__ = "property_types"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(100), nullable=False)
slug = db.Column(db.String(120), unique=True, nullable=False, index=True)
parent_id = db.Column(
db.Integer,
db.ForeignKey("property_types.id", ondelete="SET NULL"),
nullable=True,
index=True,
)
# Self-referential relationship
subtypes = db.relationship(
"PropertyType",
backref=db.backref("parent", remote_side=[id]),
lazy="select",
)
def __repr__(self) -> str:
return f"<PropertyType {self.slug!r}>"
class Amenity(db.Model):
"""Tagged feature/amenity for a property (characteristic, leisure, condo, security)"""
__tablename__ = "amenities"
GROUPS = ("caracteristica", "lazer", "condominio", "seguranca")
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(100), nullable=False)
slug = db.Column(db.String(120), unique=True, nullable=False, index=True)
group = db.Column(
db.Enum(
"caracteristica", "lazer", "condominio", "seguranca", name="amenity_group"
),
nullable=False,
)
def __repr__(self) -> str:
return f"<Amenity {self.slug!r} group={self.group!r}>"

View file

@ -0,0 +1,22 @@
from app.extensions import db
class HomepageConfig(db.Model):
__tablename__ = "homepage_config"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
hero_headline = db.Column(db.String(120), nullable=False)
hero_subheadline = db.Column(db.String(240), nullable=True)
hero_cta_label = db.Column(db.String(40), nullable=False, default="Ver Imóveis")
hero_cta_url = db.Column(db.String(200), nullable=False, default="/imoveis")
featured_properties_limit = db.Column(db.Integer, nullable=False, default=6)
hero_image_url = db.Column(db.String(512), nullable=True)
updated_at = db.Column(
db.DateTime,
nullable=False,
server_default=db.func.now(),
onupdate=db.func.now(),
)
def __repr__(self) -> str:
return f"<HomepageConfig id={self.id!r} headline={self.hero_headline!r}>"

View file

@ -0,0 +1,18 @@
from app.extensions import db
class Imobiliaria(db.Model):
__tablename__ = "imobiliarias"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(200), nullable=False)
logo_url = db.Column(db.String(512), nullable=True)
website = db.Column(db.String(512), nullable=True)
is_active = db.Column(db.Boolean, nullable=False, default=True)
display_order = db.Column(db.Integer, nullable=False, default=0)
created_at = db.Column(db.DateTime, nullable=False, server_default=db.func.now())
properties = db.relationship("Property", backref="imobiliaria", lazy="dynamic")
def __repr__(self) -> str:
return f"<Imobiliaria {self.name!r}>"

View file

@ -0,0 +1,25 @@
from app.extensions import db
class ContactLead(db.Model):
__tablename__ = "contact_leads"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
property_id = db.Column(
db.UUID(as_uuid=True),
db.ForeignKey("properties.id", ondelete="SET NULL"),
nullable=True,
index=True,
)
name = db.Column(db.String(150), nullable=False)
email = db.Column(db.String(254), nullable=False)
phone = db.Column(db.String(20), nullable=True)
message = db.Column(db.Text, nullable=False)
created_at = db.Column(
db.DateTime(timezone=True),
nullable=False,
server_default=db.func.now(),
)
def __repr__(self) -> str:
return f"<ContactLead id={self.id} email={self.email!r}>"

View file

@ -0,0 +1,46 @@
from app.extensions import db
class City(db.Model):
"""City managed via admin panel."""
__tablename__ = "cities"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(100), nullable=False)
slug = db.Column(db.String(120), unique=True, nullable=False, index=True)
state = db.Column(db.String(2), nullable=False) # UF: SP, RJ, MG, ...
neighborhoods = db.relationship(
"Neighborhood",
backref="city",
order_by="Neighborhood.name",
cascade="all, delete-orphan",
lazy="select",
)
def __repr__(self) -> str:
return f"<City {self.name!r}/{self.state!r}>"
class Neighborhood(db.Model):
"""Neighborhood (bairro) managed via admin panel."""
__tablename__ = "neighborhoods"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(100), nullable=False)
slug = db.Column(db.String(120), nullable=False, index=True)
city_id = db.Column(
db.Integer,
db.ForeignKey("cities.id", ondelete="CASCADE"),
nullable=False,
index=True,
)
__table_args__ = (
db.UniqueConstraint("slug", "city_id", name="uq_neighborhood_slug_city"),
)
def __repr__(self) -> str:
return f"<Neighborhood {self.name!r} city_id={self.city_id}>"

View file

@ -0,0 +1,21 @@
from datetime import datetime, timezone
from app.extensions import db
class PageView(db.Model):
__tablename__ = "page_views"
id = db.Column(db.Integer, primary_key=True)
path = db.Column(db.String(512), nullable=False)
property_id = db.Column(db.String(36), nullable=True, index=True)
accessed_at = db.Column(
db.DateTime(timezone=True),
nullable=False,
default=lambda: datetime.now(timezone.utc),
index=True,
)
ip_hash = db.Column(db.String(64), nullable=True)
user_agent = db.Column(db.String(512), nullable=True)
def __repr__(self) -> str:
return f"<PageView {self.path} at {self.accessed_at}>"

View file

@ -0,0 +1,91 @@
import uuid
from app.extensions import db
class Property(db.Model):
__tablename__ = "properties"
id = db.Column(db.UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
title = db.Column(db.String(200), nullable=False)
slug = db.Column(db.String(220), unique=True, nullable=False, index=True)
address = db.Column(db.String(300), nullable=True)
price = db.Column(db.Numeric(12, 2), nullable=False)
condo_fee = db.Column(db.Numeric(10, 2), nullable=True)
type = db.Column(
db.Enum("venda", "aluguel", name="property_type"),
nullable=False,
)
subtype_id = db.Column(
db.Integer,
db.ForeignKey("property_types.id", ondelete="SET NULL"),
nullable=True,
index=True,
)
bedrooms = db.Column(db.Integer, nullable=False, default=0)
bathrooms = db.Column(db.Integer, nullable=False, default=0)
parking_spots = db.Column(db.Integer, nullable=False, default=0)
parking_spots_covered = db.Column(db.Integer, nullable=False, default=0)
area_m2 = db.Column(db.Integer, nullable=False)
city_id = db.Column(
db.Integer,
db.ForeignKey("cities.id", ondelete="SET NULL"),
nullable=True,
index=True,
)
neighborhood_id = db.Column(
db.Integer,
db.ForeignKey("neighborhoods.id", ondelete="SET NULL"),
nullable=True,
index=True,
)
is_featured = db.Column(db.Boolean, nullable=False, default=False)
is_active = db.Column(db.Boolean, nullable=False, default=True)
code = db.Column(db.String(30), unique=True, nullable=True)
description = db.Column(db.Text, nullable=True)
iptu_anual = db.Column(db.Numeric(12, 2), nullable=True)
imobiliaria_id = db.Column(
db.Integer,
db.ForeignKey("imobiliarias.id", ondelete="SET NULL"),
nullable=True,
index=True,
)
created_at = db.Column(db.DateTime, nullable=False, server_default=db.func.now())
photos = db.relationship(
"PropertyPhoto",
backref="property",
order_by="PropertyPhoto.display_order",
cascade="all, delete-orphan",
lazy="select",
)
subtype = db.relationship("PropertyType", foreign_keys=[subtype_id], lazy="joined")
city = db.relationship("City", foreign_keys=[city_id], lazy="joined")
neighborhood = db.relationship(
"Neighborhood", foreign_keys=[neighborhood_id], lazy="joined"
)
amenities = db.relationship(
"Amenity",
secondary="property_amenity",
lazy="select",
)
def __repr__(self) -> str:
return f"<Property {self.slug!r}>"
class PropertyPhoto(db.Model):
__tablename__ = "property_photos"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
property_id = db.Column(
db.UUID(as_uuid=True),
db.ForeignKey("properties.id", ondelete="CASCADE"),
nullable=False,
)
url = db.Column(db.String(500), nullable=False)
alt_text = db.Column(db.String(200), nullable=False, default="")
display_order = db.Column(db.Integer, nullable=False, default=0)
def __repr__(self) -> str:
return f"<PropertyPhoto property_id={self.property_id!r} order={self.display_order}>"

View file

@ -0,0 +1,33 @@
import uuid
from datetime import datetime
from app.extensions import db
class SavedProperty(db.Model):
__tablename__ = "saved_properties"
id = db.Column(db.String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
user_id = db.Column(
db.String(36),
db.ForeignKey("client_users.id", ondelete="CASCADE"),
nullable=False,
index=True,
)
property_id = db.Column(
db.UUID(as_uuid=True),
db.ForeignKey("properties.id", ondelete="SET NULL"),
nullable=True,
index=True,
)
created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
__table_args__ = (
db.UniqueConstraint(
"user_id", "property_id", name="uq_saved_properties_user_property"
),
)
user = db.relationship(
"ClientUser", backref=db.backref("saved_properties", lazy="joined")
)
property = db.relationship("Property", foreign_keys=[property_id], lazy="joined")

View file

@ -0,0 +1,38 @@
import uuid
from datetime import datetime
from app.extensions import db
class ClientUser(db.Model):
__tablename__ = "client_users"
id = db.Column(
db.String(36),
primary_key=True,
default=lambda: str(uuid.uuid4()),
)
name = db.Column(db.String(150), nullable=False)
email = db.Column(db.String(254), unique=True, nullable=False, index=True)
password_hash = db.Column(db.String(100), nullable=False)
role = db.Column(db.String(20), nullable=False, default="client")
created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
# Contato
phone = db.Column(db.String(20), nullable=True)
whatsapp = db.Column(db.String(20), nullable=True)
# Dados pessoais
cpf = db.Column(db.String(14), nullable=True)
birth_date = db.Column(db.Date, nullable=True)
# Endereço
address_street = db.Column(db.String(200), nullable=True)
address_number = db.Column(db.String(20), nullable=True)
address_complement = db.Column(db.String(100), nullable=True)
address_neighborhood = db.Column(db.String(100), nullable=True)
address_city = db.Column(db.String(100), nullable=True)
address_state = db.Column(db.String(2), nullable=True)
address_zip = db.Column(db.String(9), nullable=True)
# Observações internas (admin)
notes = db.Column(db.Text, nullable=True)

View file

@ -0,0 +1,30 @@
import uuid
from datetime import datetime
from app.extensions import db
class VisitRequest(db.Model):
__tablename__ = "visit_requests"
id = db.Column(db.String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
user_id = db.Column(
db.String(36),
db.ForeignKey("client_users.id", ondelete="SET NULL"),
nullable=True,
index=True,
)
property_id = db.Column(
db.UUID(as_uuid=True),
db.ForeignKey("properties.id", ondelete="SET NULL"),
nullable=True,
index=True,
)
message = db.Column(db.Text, nullable=False)
status = db.Column(db.String(20), nullable=False, default="pending")
scheduled_at = db.Column(db.DateTime, nullable=True)
created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
user = db.relationship(
"ClientUser", backref=db.backref("visit_requests", lazy="select")
)
property = db.relationship("Property", foreign_keys=[property_id], lazy="joined")