Files
classifieds/app/blueprints/messaging/routes.py
T
2026-06-15 14:19:14 -04:00

123 lines
5.1 KiB
Python

"""Messaging + Favorites routes."""
from flask import (Blueprint, render_template, redirect, url_for,
flash, abort, request, jsonify)
from flask_login import login_required, current_user
from flask_babel import gettext as _
from app.extensions import db, limiter
from app.models.listing import Listing
from app.models.messaging import Conversation
from app.services import messaging as msvc
from app.services import favorites as fsvc
from app.services.contact import contact_revealed, mask_body
from app.blueprints.messaging.forms import MessageForm
messaging_bp = Blueprint("messaging", __name__)
# ── Inbox ──────────────────────────────────────────────────────────────────
@messaging_bp.route("/messages")
@login_required
def inbox():
page = request.args.get("page", 1, type=int)
pagination = msvc.inbox(current_user, page=page)
return render_template("messaging/inbox.html",
pagination=pagination,
unread_total=msvc.total_unread(current_user))
# ── Conversation thread ────────────────────────────────────────────────────
@messaging_bp.route("/messages/<int:conv_id>", methods=["GET", "POST"])
@login_required
def conversation(conv_id):
conv = db.session.get(Conversation, conv_id)
if conv is None:
abort(404)
if current_user.id not in (conv.buyer_id, conv.seller_id):
abort(403)
msvc.mark_conversation_read(conv, current_user)
reveal = contact_revealed(current_user)
form = MessageForm()
if form.validate_on_submit():
try:
msvc.send_message(conv, current_user, form.body.data)
return redirect(url_for("messaging.conversation", conv_id=conv.id))
except msvc.MessagingError as e:
flash(str(e), "danger")
# mask contact info in messages for low-trust users
masked_messages = [
(msg, mask_body(msg.body, reveal=reveal))
for msg in conv.messages
]
return render_template("messaging/conversation.html",
conv=conv, form=form,
masked_messages=masked_messages,
reveal=reveal,
other=conv.other_party(current_user))
# ── Start conversation from listing detail ─────────────────────────────────
@messaging_bp.route("/listings/<int:listing_id>/contact", methods=["GET", "POST"])
@login_required
@limiter.limit("20 per hour", methods=["POST"])
def start_conversation(listing_id):
listing = db.session.get(Listing, listing_id)
if listing is None:
abort(404)
if listing.user_id == current_user.id:
flash(_("You cannot message your own listing."), "warning")
return redirect(url_for("listings.detail", listing_id=listing_id))
try:
conv, created = msvc.get_or_create_conversation(listing, current_user)
except msvc.MessagingError as e:
flash(str(e), "danger")
return redirect(url_for("listings.detail", listing_id=listing_id))
form = MessageForm()
if form.validate_on_submit():
try:
msvc.send_message(conv, current_user, form.body.data)
flash(_("Message sent."), "success")
return redirect(url_for("messaging.conversation", conv_id=conv.id))
except msvc.MessagingError as e:
flash(str(e), "danger")
return render_template("messaging/start.html",
listing=listing, conv=conv, form=form,
created=created)
# ── Favorites ──────────────────────────────────────────────────────────────
@messaging_bp.route("/listings/<int:listing_id>/favorite", methods=["POST"])
@login_required
def toggle_favorite(listing_id):
listing = db.session.get(Listing, listing_id)
if listing is None:
abort(404)
now_fav = fsvc.toggle_favorite(current_user.id, listing_id)
# AJAX or form fallback
if request.headers.get("X-Requested-With") == "XMLHttpRequest":
return jsonify(favorited=now_fav)
flash(_("Saved.") if now_fav else _("Removed from saved."), "info")
return redirect(request.referrer or url_for("listings.detail",
listing_id=listing_id))
@messaging_bp.route("/my/favorites")
@login_required
def my_favorites():
page = request.args.get("page", 1, type=int)
pagination = fsvc.user_favorites(current_user.id, page=page)
return render_template("messaging/favorites.html", pagination=pagination)
# ── Unread count API (for nav badge polling) ───────────────────────────────
@messaging_bp.route("/api/unread")
@login_required
def unread_count():
return jsonify(unread=msvc.total_unread(current_user))