"""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/", 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//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//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))