126 lines
5.3 KiB
Python
126 lines
5.3 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
|
|
from app.utils import safe_referrer
|
|
|
|
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 controls the "contact info hidden" banner for the current reader
|
|
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 is based on the *sender's* trust so a low-trust sender cannot
|
|
# slip contact info through to a trusted reader.
|
|
masked_messages = [
|
|
(msg, mask_body(msg.body, reveal=contact_revealed(msg.sender)))
|
|
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(safe_referrer(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))
|