Modules now requiring MEMBER role or higher: - NordaGPT (/chat) - with dedicated landing page for non-members - Wiadomości (/wiadomosci) - private messaging - Tablica B2B (/tablica) - business classifieds - Kontakty (/kontakty) - member contact information Non-members see a promotional page explaining the benefits of NordaGPT membership instead of being simply redirected. This provides clear value proposition for NORDA membership while protecting member-exclusive features. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
405 lines
13 KiB
Python
405 lines
13 KiB
Python
"""
|
|
Messages Routes
|
|
===============
|
|
|
|
Private messages and notifications API.
|
|
"""
|
|
|
|
from datetime import datetime
|
|
|
|
from flask import render_template, request, redirect, url_for, flash, jsonify
|
|
from flask_login import login_required, current_user
|
|
|
|
from . import bp
|
|
from database import SessionLocal, User, PrivateMessage, UserNotification, UserBlock, Classified
|
|
from utils.helpers import sanitize_input
|
|
from utils.decorators import member_required
|
|
|
|
|
|
# ============================================================
|
|
# PRIVATE MESSAGES ROUTES
|
|
# ============================================================
|
|
|
|
@bp.route('/wiadomosci')
|
|
@login_required
|
|
@member_required
|
|
def messages_inbox():
|
|
"""Skrzynka odbiorcza"""
|
|
page = request.args.get('page', 1, type=int)
|
|
per_page = 20
|
|
|
|
db = SessionLocal()
|
|
try:
|
|
query = db.query(PrivateMessage).filter(
|
|
PrivateMessage.recipient_id == current_user.id
|
|
).order_by(PrivateMessage.created_at.desc())
|
|
|
|
total = query.count()
|
|
messages = query.limit(per_page).offset((page - 1) * per_page).all()
|
|
|
|
unread_count = db.query(PrivateMessage).filter(
|
|
PrivateMessage.recipient_id == current_user.id,
|
|
PrivateMessage.is_read == False
|
|
).count()
|
|
|
|
return render_template('messages/inbox.html',
|
|
messages=messages,
|
|
page=page,
|
|
total_pages=(total + per_page - 1) // per_page,
|
|
unread_count=unread_count
|
|
)
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
@bp.route('/wiadomosci/wyslane')
|
|
@login_required
|
|
@member_required
|
|
def messages_sent():
|
|
"""Wysłane wiadomości"""
|
|
page = request.args.get('page', 1, type=int)
|
|
per_page = 20
|
|
|
|
db = SessionLocal()
|
|
try:
|
|
query = db.query(PrivateMessage).filter(
|
|
PrivateMessage.sender_id == current_user.id
|
|
).order_by(PrivateMessage.created_at.desc())
|
|
|
|
total = query.count()
|
|
messages = query.limit(per_page).offset((page - 1) * per_page).all()
|
|
|
|
return render_template('messages/sent.html',
|
|
messages=messages,
|
|
page=page,
|
|
total_pages=(total + per_page - 1) // per_page
|
|
)
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
@bp.route('/wiadomosci/nowa')
|
|
@login_required
|
|
@member_required
|
|
def messages_new():
|
|
"""Formularz nowej wiadomości"""
|
|
recipient_id = request.args.get('to', type=int)
|
|
context_type = request.args.get('context_type')
|
|
context_id = request.args.get('context_id', type=int)
|
|
|
|
db = SessionLocal()
|
|
try:
|
|
# Lista użytkowników do wyboru
|
|
users = db.query(User).filter(
|
|
User.is_active == True,
|
|
User.is_verified == True,
|
|
User.id != current_user.id
|
|
).order_by(User.name).all()
|
|
|
|
recipient = None
|
|
if recipient_id:
|
|
recipient = db.query(User).filter(User.id == recipient_id).first()
|
|
|
|
# Pobierz kontekst (np. ogłoszenie B2B)
|
|
context = None
|
|
context_subject = None
|
|
if context_type == 'classified' and context_id:
|
|
classified = db.query(Classified).filter(Classified.id == context_id).first()
|
|
if classified:
|
|
context = {
|
|
'type': 'classified',
|
|
'id': classified.id,
|
|
'title': classified.title,
|
|
'url': url_for('classifieds.classifieds_view', classified_id=classified.id)
|
|
}
|
|
context_subject = f"Dotyczy: {classified.title}"
|
|
|
|
return render_template('messages/compose.html',
|
|
users=users,
|
|
recipient=recipient,
|
|
context=context,
|
|
context_type=context_type,
|
|
context_id=context_id,
|
|
context_subject=context_subject
|
|
)
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
@bp.route('/wiadomosci/wyslij', methods=['POST'])
|
|
@login_required
|
|
@member_required
|
|
def messages_send():
|
|
"""Wyślij wiadomość"""
|
|
recipient_id = request.form.get('recipient_id', type=int)
|
|
subject = sanitize_input(request.form.get('subject', ''), 255)
|
|
content = request.form.get('content', '').strip()
|
|
context_type = request.form.get('context_type')
|
|
context_id = request.form.get('context_id', type=int)
|
|
|
|
if not recipient_id or not content:
|
|
flash('Odbiorca i treść są wymagane.', 'error')
|
|
return redirect(url_for('.messages_new'))
|
|
|
|
db = SessionLocal()
|
|
try:
|
|
recipient = db.query(User).filter(User.id == recipient_id).first()
|
|
if not recipient:
|
|
flash('Odbiorca nie istnieje.', 'error')
|
|
return redirect(url_for('.messages_new'))
|
|
|
|
# Check if either user has blocked the other
|
|
block_exists = db.query(UserBlock).filter(
|
|
((UserBlock.user_id == current_user.id) & (UserBlock.blocked_user_id == recipient_id)) |
|
|
((UserBlock.user_id == recipient_id) & (UserBlock.blocked_user_id == current_user.id))
|
|
).first()
|
|
if block_exists:
|
|
flash('Nie można wysłać wiadomości do tego użytkownika.', 'error')
|
|
return redirect(url_for('.messages_new'))
|
|
|
|
message = PrivateMessage(
|
|
sender_id=current_user.id,
|
|
recipient_id=recipient_id,
|
|
subject=subject,
|
|
content=content,
|
|
context_type=context_type if context_type else None,
|
|
context_id=context_id if context_id else None
|
|
)
|
|
db.add(message)
|
|
db.commit()
|
|
|
|
flash('Wiadomość wysłana.', 'success')
|
|
return redirect(url_for('.messages_sent'))
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
@bp.route('/wiadomosci/<int:message_id>')
|
|
@login_required
|
|
@member_required
|
|
def messages_view(message_id):
|
|
"""Czytaj wiadomość"""
|
|
db = SessionLocal()
|
|
try:
|
|
message = db.query(PrivateMessage).filter(
|
|
PrivateMessage.id == message_id
|
|
).first()
|
|
|
|
if not message:
|
|
flash('Wiadomość nie istnieje.', 'error')
|
|
return redirect(url_for('.messages_inbox'))
|
|
|
|
# Sprawdź dostęp
|
|
if message.recipient_id != current_user.id and message.sender_id != current_user.id:
|
|
flash('Brak dostępu do tej wiadomości.', 'error')
|
|
return redirect(url_for('.messages_inbox'))
|
|
|
|
# Oznacz jako przeczytaną
|
|
if message.recipient_id == current_user.id and not message.is_read:
|
|
message.is_read = True
|
|
message.read_at = datetime.now()
|
|
db.commit()
|
|
|
|
# Pobierz kontekst (np. ogłoszenie B2B)
|
|
context = None
|
|
if message.context_type == 'classified' and message.context_id:
|
|
classified = db.query(Classified).filter(Classified.id == message.context_id).first()
|
|
if classified:
|
|
context = {
|
|
'type': 'classified',
|
|
'id': classified.id,
|
|
'title': classified.title,
|
|
'url': url_for('classifieds.classifieds_view', classified_id=classified.id),
|
|
'is_active': classified.is_active
|
|
}
|
|
|
|
return render_template('messages/view.html', message=message, context=context)
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
@bp.route('/wiadomosci/<int:message_id>/odpowiedz', methods=['POST'])
|
|
@login_required
|
|
@member_required
|
|
def messages_reply(message_id):
|
|
"""Odpowiedz na wiadomość"""
|
|
content = request.form.get('content', '').strip()
|
|
|
|
if not content:
|
|
flash('Treść jest wymagana.', 'error')
|
|
return redirect(url_for('.messages_view', message_id=message_id))
|
|
|
|
db = SessionLocal()
|
|
try:
|
|
original = db.query(PrivateMessage).filter(
|
|
PrivateMessage.id == message_id
|
|
).first()
|
|
|
|
if not original:
|
|
flash('Wiadomość nie istnieje.', 'error')
|
|
return redirect(url_for('.messages_inbox'))
|
|
|
|
# Odpowiedz do nadawcy oryginalnej wiadomości
|
|
recipient_id = original.sender_id if original.sender_id != current_user.id else original.recipient_id
|
|
|
|
# Check if either user has blocked the other
|
|
block_exists = db.query(UserBlock).filter(
|
|
((UserBlock.user_id == current_user.id) & (UserBlock.blocked_user_id == recipient_id)) |
|
|
((UserBlock.user_id == recipient_id) & (UserBlock.blocked_user_id == current_user.id))
|
|
).first()
|
|
if block_exists:
|
|
flash('Nie można wysłać wiadomości do tego użytkownika.', 'error')
|
|
return redirect(url_for('.messages_inbox'))
|
|
|
|
reply = PrivateMessage(
|
|
sender_id=current_user.id,
|
|
recipient_id=recipient_id,
|
|
subject=f"Re: {original.subject}" if original.subject else None,
|
|
content=content,
|
|
parent_id=message_id
|
|
)
|
|
db.add(reply)
|
|
db.commit()
|
|
|
|
flash('Odpowiedź wysłana.', 'success')
|
|
return redirect(url_for('.messages_view', message_id=message_id))
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
@bp.route('/api/messages/unread-count')
|
|
@login_required
|
|
@member_required
|
|
def api_unread_count():
|
|
"""API: Liczba nieprzeczytanych wiadomości"""
|
|
db = SessionLocal()
|
|
try:
|
|
count = db.query(PrivateMessage).filter(
|
|
PrivateMessage.recipient_id == current_user.id,
|
|
PrivateMessage.is_read == False
|
|
).count()
|
|
return jsonify({'count': count})
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
# ============================================================
|
|
# NOTIFICATIONS API ROUTES
|
|
# ============================================================
|
|
|
|
@bp.route('/api/notifications')
|
|
@login_required
|
|
@member_required
|
|
def api_notifications():
|
|
"""API: Get user notifications"""
|
|
limit = request.args.get('limit', 20, type=int)
|
|
offset = request.args.get('offset', 0, type=int)
|
|
unread_only = request.args.get('unread_only', 'false').lower() == 'true'
|
|
|
|
db = SessionLocal()
|
|
try:
|
|
query = db.query(UserNotification).filter(
|
|
UserNotification.user_id == current_user.id
|
|
)
|
|
|
|
if unread_only:
|
|
query = query.filter(UserNotification.is_read == False)
|
|
|
|
# Order by most recent first
|
|
query = query.order_by(UserNotification.created_at.desc())
|
|
|
|
total = query.count()
|
|
notifications = query.limit(limit).offset(offset).all()
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'notifications': [
|
|
{
|
|
'id': n.id,
|
|
'title': n.title,
|
|
'message': n.message,
|
|
'notification_type': n.notification_type,
|
|
'related_type': n.related_type,
|
|
'related_id': n.related_id,
|
|
'action_url': n.action_url,
|
|
'is_read': n.is_read,
|
|
'created_at': n.created_at.isoformat() if n.created_at else None
|
|
}
|
|
for n in notifications
|
|
],
|
|
'total': total,
|
|
'unread_count': db.query(UserNotification).filter(
|
|
UserNotification.user_id == current_user.id,
|
|
UserNotification.is_read == False
|
|
).count()
|
|
})
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
@bp.route('/api/notifications/<int:notification_id>/read', methods=['POST'])
|
|
@login_required
|
|
@member_required
|
|
def api_notification_mark_read(notification_id):
|
|
"""API: Mark notification as read"""
|
|
db = SessionLocal()
|
|
try:
|
|
notification = db.query(UserNotification).filter(
|
|
UserNotification.id == notification_id,
|
|
UserNotification.user_id == current_user.id
|
|
).first()
|
|
|
|
if not notification:
|
|
return jsonify({'success': False, 'error': 'Powiadomienie nie znalezione'}), 404
|
|
|
|
notification.mark_as_read()
|
|
db.commit()
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'message': 'Oznaczono jako przeczytane'
|
|
})
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
@bp.route('/api/notifications/read-all', methods=['POST'])
|
|
@login_required
|
|
@member_required
|
|
def api_notifications_mark_all_read():
|
|
"""API: Mark all notifications as read"""
|
|
db = SessionLocal()
|
|
try:
|
|
updated = db.query(UserNotification).filter(
|
|
UserNotification.user_id == current_user.id,
|
|
UserNotification.is_read == False
|
|
).update({
|
|
UserNotification.is_read: True,
|
|
UserNotification.read_at: datetime.now()
|
|
})
|
|
db.commit()
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'message': f'Oznaczono {updated} powiadomien jako przeczytane',
|
|
'count': updated
|
|
})
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
@bp.route('/api/notifications/unread-count')
|
|
@login_required
|
|
@member_required
|
|
def api_notifications_unread_count():
|
|
"""API: Get unread notifications count"""
|
|
db = SessionLocal()
|
|
try:
|
|
count = db.query(UserNotification).filter(
|
|
UserNotification.user_id == current_user.id,
|
|
UserNotification.is_read == False
|
|
).count()
|
|
return jsonify({'count': count})
|
|
finally:
|
|
db.close()
|