feat: add notifications for B2B classifieds (questions, answers, interest)
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions
Three new notification types: - New question → author gets in-app + email - Answer to question → questioner gets in-app + email - Someone interested → author gets in-app only Previously the B2B board had zero notifications, so authors never knew someone asked a question about their listing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7bd292d3b3
commit
d463f4b6df
@ -14,6 +14,13 @@ from database import SessionLocal, Classified, ClassifiedRead, ClassifiedInteres
|
||||
from sqlalchemy import desc
|
||||
from utils.helpers import sanitize_input
|
||||
from utils.decorators import member_required
|
||||
from utils.notifications import (
|
||||
create_classified_question_notification,
|
||||
create_classified_answer_notification,
|
||||
create_classified_interest_notification,
|
||||
send_classified_question_email,
|
||||
send_classified_answer_email,
|
||||
)
|
||||
|
||||
|
||||
@bp.route('/', endpoint='classifieds_index')
|
||||
@ -309,6 +316,16 @@ def toggle_interest(classified_id):
|
||||
)
|
||||
db.add(interest)
|
||||
db.commit()
|
||||
|
||||
# Notify classified author (in-app only)
|
||||
if classified.author_id != current_user.id:
|
||||
try:
|
||||
interested_name = current_user.name or current_user.email.split('@')[0]
|
||||
create_classified_interest_notification(
|
||||
classified_id, classified.title, interested_name, classified.author_id)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to send classified interest notification: {e}")
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'interested': True,
|
||||
@ -399,6 +416,20 @@ def ask_question(classified_id):
|
||||
db.add(question)
|
||||
db.commit()
|
||||
|
||||
# Notify classified author (in-app + email)
|
||||
if classified.author_id != current_user.id:
|
||||
questioner_name = current_user.name or current_user.email.split('@')[0]
|
||||
try:
|
||||
create_classified_question_notification(
|
||||
classified_id, classified.title, questioner_name, classified.author_id)
|
||||
author = db.query(User).filter(User.id == classified.author_id).first()
|
||||
if author and author.email and author.notify_email_messages != False:
|
||||
send_classified_question_email(
|
||||
classified_id, classified.title, questioner_name, content,
|
||||
author.email, author.name or author.email.split('@')[0])
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to send classified question notification: {e}")
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': 'Pytanie dodane',
|
||||
@ -456,6 +487,20 @@ def answer_question(classified_id, question_id):
|
||||
question.answered_at = datetime.now()
|
||||
db.commit()
|
||||
|
||||
# Notify question author (in-app + email)
|
||||
if question.author_id != current_user.id:
|
||||
answerer_name = current_user.name or current_user.email.split('@')[0]
|
||||
try:
|
||||
create_classified_answer_notification(
|
||||
classified_id, classified.title, answerer_name, question.author_id)
|
||||
q_author = db.query(User).filter(User.id == question.author_id).first()
|
||||
if q_author and q_author.email and q_author.notify_email_messages != False:
|
||||
send_classified_answer_email(
|
||||
classified_id, classified.title, answerer_name, answer,
|
||||
q_author.email, q_author.name or q_author.email.split('@')[0])
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to send classified answer notification: {e}")
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': 'Odpowiedź dodana',
|
||||
|
||||
@ -341,6 +341,173 @@ Norda Biznes Partner - https://nordabiznes.pl
|
||||
return count
|
||||
|
||||
|
||||
# ============================================================
|
||||
# B2B CLASSIFIEDS NOTIFICATIONS
|
||||
# ============================================================
|
||||
|
||||
def create_classified_question_notification(classified_id, classified_title, questioner_name, author_id):
|
||||
"""Notify classified author that someone asked a question."""
|
||||
return create_notification(
|
||||
user_id=author_id,
|
||||
title="Nowe pytanie do ogłoszenia B2B",
|
||||
message=f"{questioner_name} zadał pytanie do ogłoszenia: {classified_title[:50]}{'...' if len(classified_title) > 50 else ''}",
|
||||
notification_type='message',
|
||||
related_type='classified_question',
|
||||
related_id=classified_id,
|
||||
action_url=f'/tablica/{classified_id}'
|
||||
)
|
||||
|
||||
|
||||
def create_classified_answer_notification(classified_id, classified_title, answerer_name, questioner_id):
|
||||
"""Notify question author that the classified owner answered."""
|
||||
return create_notification(
|
||||
user_id=questioner_id,
|
||||
title="Odpowiedź na Twoje pytanie B2B",
|
||||
message=f"{answerer_name} odpowiedział na Twoje pytanie w ogłoszeniu: {classified_title[:50]}{'...' if len(classified_title) > 50 else ''}",
|
||||
notification_type='message',
|
||||
related_type='classified_answer',
|
||||
related_id=classified_id,
|
||||
action_url=f'/tablica/{classified_id}'
|
||||
)
|
||||
|
||||
|
||||
def create_classified_interest_notification(classified_id, classified_title, interested_name, author_id):
|
||||
"""Notify classified author that someone is interested."""
|
||||
return create_notification(
|
||||
user_id=author_id,
|
||||
title="Ktoś zainteresował się Twoim ogłoszeniem",
|
||||
message=f"{interested_name} wyraził zainteresowanie ogłoszeniem: {classified_title[:50]}{'...' if len(classified_title) > 50 else ''}",
|
||||
notification_type='message',
|
||||
related_type='classified_interest',
|
||||
related_id=classified_id,
|
||||
action_url=f'/tablica/{classified_id}'
|
||||
)
|
||||
|
||||
|
||||
def send_classified_question_email(classified_id, classified_title, questioner_name, question_content, author_email, author_name):
|
||||
"""Send email to classified author about a new question."""
|
||||
from email_service import send_email, _email_v3_wrap
|
||||
|
||||
base_url = "https://nordabiznes.pl"
|
||||
classified_url = f"{base_url}/tablica/{classified_id}"
|
||||
|
||||
preview = question_content[:200].strip()
|
||||
if len(question_content) > 200:
|
||||
preview += "..."
|
||||
|
||||
subject = f"Nowe pytanie do ogłoszenia: {classified_title[:60]}"
|
||||
|
||||
body_text = f"""{questioner_name} zadał pytanie do Twojego ogłoszenia: {classified_title}
|
||||
|
||||
"{preview}"
|
||||
|
||||
Zobacz i odpowiedz: {classified_url}
|
||||
|
||||
---
|
||||
Norda Biznes Partner - https://nordabiznes.pl
|
||||
"""
|
||||
|
||||
content = f'''
|
||||
<p style="margin:0 0 16px; color:#1e293b; font-size:16px;">Cześć <strong>{author_name}</strong>!</p>
|
||||
<p style="margin:0 0 24px; color:#475569; font-size:15px; line-height:1.5;"><strong>{questioner_name}</strong> zadał pytanie do Twojego ogłoszenia na tablicy B2B:</p>
|
||||
|
||||
<table width="100%" cellpadding="0" cellspacing="0" style="background:#f8fafc; border-radius:10px; border: 1px solid #e2e8f0; margin-bottom:24px;">
|
||||
<tr><td style="padding: 16px 20px;">
|
||||
<p style="margin:0 0 4px; color:#64748b; font-size:12px; text-transform:uppercase; letter-spacing:0.5px;">Ogłoszenie</p>
|
||||
<p style="margin:0; color:#1e3a8a; font-size:17px; font-weight:600;">{classified_title}</p>
|
||||
</td></tr>
|
||||
</table>
|
||||
|
||||
<table width="100%" cellpadding="0" cellspacing="0" style="background:#f1f5f9; border-left: 4px solid #f59e0b; border-radius: 0 8px 8px 0; margin-bottom:28px;">
|
||||
<tr><td style="padding: 16px 20px;">
|
||||
<p style="margin:0; color:#475569; font-size:14px; font-style:italic; line-height:1.6;">{preview}</p>
|
||||
</td></tr>
|
||||
</table>
|
||||
|
||||
<table width="100%" cellpadding="0" cellspacing="0" style="margin-bottom:20px;">
|
||||
<tr><td align="center" style="padding: 8px 0;">
|
||||
<a href="{classified_url}" style="display:inline-block; padding:16px 40px; background: linear-gradient(135deg, #1e3a8a, #172554); color:#ffffff; text-decoration:none; border-radius:8px; font-size:15px; font-weight:600;">Zobacz pytanie i odpowiedz →</a>
|
||||
</td></tr>
|
||||
</table>'''
|
||||
|
||||
body_html = _email_v3_wrap('Nowe pytanie do ogłoszenia B2B', 'Norda Biznes Partner', content)
|
||||
|
||||
try:
|
||||
return send_email(
|
||||
to=[author_email],
|
||||
subject=subject,
|
||||
body_text=body_text,
|
||||
body_html=body_html,
|
||||
email_type='classified_notification',
|
||||
recipient_name=author_name
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to send classified question email to {author_email}: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def send_classified_answer_email(classified_id, classified_title, answerer_name, answer_content, questioner_email, questioner_name):
|
||||
"""Send email to question author about an answer."""
|
||||
from email_service import send_email, _email_v3_wrap
|
||||
|
||||
base_url = "https://nordabiznes.pl"
|
||||
classified_url = f"{base_url}/tablica/{classified_id}"
|
||||
|
||||
preview = answer_content[:200].strip()
|
||||
if len(answer_content) > 200:
|
||||
preview += "..."
|
||||
|
||||
subject = f"Odpowiedź na Twoje pytanie: {classified_title[:60]}"
|
||||
|
||||
body_text = f"""{answerer_name} odpowiedział na Twoje pytanie w ogłoszeniu: {classified_title}
|
||||
|
||||
"{preview}"
|
||||
|
||||
Zobacz odpowiedź: {classified_url}
|
||||
|
||||
---
|
||||
Norda Biznes Partner - https://nordabiznes.pl
|
||||
"""
|
||||
|
||||
content = f'''
|
||||
<p style="margin:0 0 16px; color:#1e293b; font-size:16px;">Cześć <strong>{questioner_name}</strong>!</p>
|
||||
<p style="margin:0 0 24px; color:#475569; font-size:15px; line-height:1.5;"><strong>{answerer_name}</strong> odpowiedział na Twoje pytanie w ogłoszeniu:</p>
|
||||
|
||||
<table width="100%" cellpadding="0" cellspacing="0" style="background:#f8fafc; border-radius:10px; border: 1px solid #e2e8f0; margin-bottom:24px;">
|
||||
<tr><td style="padding: 16px 20px;">
|
||||
<p style="margin:0 0 4px; color:#64748b; font-size:12px; text-transform:uppercase; letter-spacing:0.5px;">Ogłoszenie</p>
|
||||
<p style="margin:0; color:#1e3a8a; font-size:17px; font-weight:600;">{classified_title}</p>
|
||||
</td></tr>
|
||||
</table>
|
||||
|
||||
<table width="100%" cellpadding="0" cellspacing="0" style="background:#f1f5f9; border-left: 4px solid #10b981; border-radius: 0 8px 8px 0; margin-bottom:28px;">
|
||||
<tr><td style="padding: 16px 20px;">
|
||||
<p style="margin:0; color:#475569; font-size:14px; font-style:italic; line-height:1.6;">{preview}</p>
|
||||
</td></tr>
|
||||
</table>
|
||||
|
||||
<table width="100%" cellpadding="0" cellspacing="0" style="margin-bottom:20px;">
|
||||
<tr><td align="center" style="padding: 8px 0;">
|
||||
<a href="{classified_url}" style="display:inline-block; padding:16px 40px; background: linear-gradient(135deg, #1e3a8a, #172554); color:#ffffff; text-decoration:none; border-radius:8px; font-size:15px; font-weight:600;">Zobacz odpowiedź →</a>
|
||||
</td></tr>
|
||||
</table>'''
|
||||
|
||||
body_html = _email_v3_wrap('Odpowiedź na pytanie B2B', 'Norda Biznes Partner', content)
|
||||
|
||||
try:
|
||||
return send_email(
|
||||
to=[questioner_email],
|
||||
subject=subject,
|
||||
body_text=body_text,
|
||||
body_html=body_html,
|
||||
email_type='classified_notification',
|
||||
recipient_name=questioner_name
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to send classified answer email to {questioner_email}: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def create_forum_reaction_notification(user_id, reactor_name, content_type, content_id, topic_id, emoji):
|
||||
"""
|
||||
Notify user when someone reacts to their content.
|
||||
|
||||
Loading…
Reference in New Issue
Block a user