From 3f1e66d3cac967341df128096657af5f3f452871 Mon Sep 17 00:00:00 2001 From: Maciej Pienczyn Date: Tue, 14 Apr 2026 17:50:41 +0200 Subject: [PATCH] =?UTF-8?q?feat(email):=20per-typ=20preferencje=20powiadom?= =?UTF-8?q?ie=C5=84=20e-mail=20(D.1=20dope=C5=82nienie)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Symetria z push — panel /konto/prywatnosc rozszerzony o 3 dodatkowe toggle w karcie "Powiadomienia e-mail": - Pytanie pod moim ogłoszeniem B2B (notify_email_classified_question) - Odpowiedź pod moim pytaniem B2B (notify_email_classified_answer) - Ogłoszenie wygasa za 3 dni (notify_email_classified_expiry) Migracja 102 dodaje kolumny (default TRUE — nie zmienia zachowania istniejących userów). Endpointy ask_question / answer_question teraz czytają dedykowaną flagę zamiast notify_email_messages (która zostaje tylko dla wiadomości prywatnych). Skrypt classified_expiry_notifier.py pomija userów z wyłączonym notify_email_classified_expiry. W kolejnych sub-fazach D.2/D.3 symetrycznie dojdą triggery e-mail + toggle dla forum/broadcast/wydarzeń — z defaults dobranymi tak, by nie zalać inbox użytkowników (broadcast OFF, personalne ON). Co-Authored-By: Claude Opus 4.6 (1M context) --- blueprints/auth/routes.py | 3 ++ blueprints/community/classifieds/routes.py | 4 +-- database.py | 7 ++-- .../migrations/102_add_email_preferences.sql | 10 ++++++ scripts/classified_expiry_notifier.py | 5 +++ templates/konto/prywatnosc.html | 33 +++++++++++++++++++ 6 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 database/migrations/102_add_email_preferences.sql diff --git a/blueprints/auth/routes.py b/blueprints/auth/routes.py index b996900..49dbabd 100644 --- a/blueprints/auth/routes.py +++ b/blueprints/auth/routes.py @@ -914,6 +914,9 @@ def konto_prywatnosc(): user.contact_prefer_phone = request.form.get('prefer_phone') == 'on' user.contact_prefer_portal = request.form.get('prefer_portal') == 'on' user.notify_email_messages = request.form.get('notify_email_messages') == 'on' + user.notify_email_classified_question = request.form.get('notify_email_classified_question') == 'on' + user.notify_email_classified_answer = request.form.get('notify_email_classified_answer') == 'on' + user.notify_email_classified_expiry = request.form.get('notify_email_classified_expiry') == 'on' # Web Push preferences per event type user.notify_push_messages = request.form.get('notify_push_messages') == 'on' diff --git a/blueprints/community/classifieds/routes.py b/blueprints/community/classifieds/routes.py index 44e301e..63a6a84 100644 --- a/blueprints/community/classifieds/routes.py +++ b/blueprints/community/classifieds/routes.py @@ -635,7 +635,7 @@ def ask_question(classified_id): 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: + if author and author.email and author.notify_email_classified_question is not False: send_classified_question_email( classified_id, classified.title, questioner_name, content, author.email, author.name or author.email.split('@')[0]) @@ -720,7 +720,7 @@ def answer_question(classified_id, question_id): 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: + if q_author and q_author.email and q_author.notify_email_classified_answer is not False: send_classified_answer_email( classified_id, classified.title, answerer_name, answer, q_author.email, q_author.name or q_author.email.split('@')[0]) diff --git a/database.py b/database.py index 8dfef7b..3194940 100644 --- a/database.py +++ b/database.py @@ -337,8 +337,11 @@ class User(Base, UserMixin): contact_prefer_portal = Column(Boolean, default=True) # User prefers portal messages contact_note = Column(Text, nullable=True) # Additional note (e.g. best hours) - # Email notification preferences - notify_email_messages = Column(Boolean, default=True) # Email when receiving private message + # Email notification preferences (per event type) + notify_email_messages = Column(Boolean, default=True) # Prywatna wiadomość + notify_email_classified_question = Column(Boolean, default=True) # Pytanie pod moim ogłoszeniem B2B + notify_email_classified_answer = Column(Boolean, default=True) # Odpowiedź pod moim pytaniem B2B + notify_email_classified_expiry = Column(Boolean, default=True) # Moje ogłoszenie wygasa za 3 dni # Web Push notification preferences (per event type) notify_push_messages = Column(Boolean, default=True) # Prywatna wiadomość diff --git a/database/migrations/102_add_email_preferences.sql b/database/migrations/102_add_email_preferences.sql new file mode 100644 index 0000000..762744b --- /dev/null +++ b/database/migrations/102_add_email_preferences.sql @@ -0,0 +1,10 @@ +-- Migration 102: per-event e-mail notification preferences +-- +-- Dotąd użytkownik miał jedną flagę notify_email_messages (używana także przez +-- classified question/answer). Rozbijamy na dedykowane flagi per typ e-maila. +-- Wszystkie default TRUE — zachowujemy obecne zachowanie dla istniejących userów. + +ALTER TABLE users + ADD COLUMN IF NOT EXISTS notify_email_classified_question BOOLEAN DEFAULT TRUE, + ADD COLUMN IF NOT EXISTS notify_email_classified_answer BOOLEAN DEFAULT TRUE, + ADD COLUMN IF NOT EXISTS notify_email_classified_expiry BOOLEAN DEFAULT TRUE; diff --git a/scripts/classified_expiry_notifier.py b/scripts/classified_expiry_notifier.py index f680d4c..0442193 100644 --- a/scripts/classified_expiry_notifier.py +++ b/scripts/classified_expiry_notifier.py @@ -50,6 +50,11 @@ def main(): if not author or not author.email: continue + # Respect user preference + if getattr(author, 'notify_email_classified_expiry', True) is False: + print(f" [pomijam] {classified.title} -> {author.email} (wyłączył powiadomienia e-mail o wygasaniu)") + continue + author_name = author.name or author.email.split('@')[0] expire_date = classified.expires_at.strftime('%d.%m.%Y') extend_url = f"https://nordabiznes.pl/tablica/{classified.id}" diff --git a/templates/konto/prywatnosc.html b/templates/konto/prywatnosc.html index 51c499b..1c79eb9 100644 --- a/templates/konto/prywatnosc.html +++ b/templates/konto/prywatnosc.html @@ -367,6 +367,39 @@ + +
+
+
Pytanie do Twojego ogłoszenia B2B
+
E-mail gdy ktoś zada publiczne pytanie pod Twoim ogłoszeniem
+
+ +
+ +
+
+
Odpowiedź na Twoje pytanie B2B
+
E-mail gdy autor ogłoszenia odpowie na Twoje pytanie
+
+ +
+ +
+
+
Twoje ogłoszenie wygasa za 3 dni
+
Przypomnienie e-mailem gdy Twoje ogłoszenie B2B zbliża się do daty wygaśnięcia (abyś mógł je przedłużyć)
+
+ +