feat(email): per-typ preferencje powiadomień e-mail (D.1 dopełnienie)
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
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) <noreply@anthropic.com>
This commit is contained in:
parent
46adb0ece7
commit
3f1e66d3ca
@ -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'
|
||||
|
||||
@ -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])
|
||||
|
||||
@ -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ść
|
||||
|
||||
10
database/migrations/102_add_email_preferences.sql
Normal file
10
database/migrations/102_add_email_preferences.sql
Normal file
@ -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;
|
||||
@ -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}"
|
||||
|
||||
@ -367,6 +367,39 @@
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="setting-item">
|
||||
<div class="setting-info">
|
||||
<div class="setting-label">Pytanie do Twojego ogłoszenia B2B</div>
|
||||
<div class="setting-description">E-mail gdy ktoś zada publiczne pytanie pod Twoim ogłoszeniem</div>
|
||||
</div>
|
||||
<label class="toggle-switch">
|
||||
<input type="checkbox" name="notify_email_classified_question" {% if user.notify_email_classified_question != False %}checked{% endif %}>
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="setting-item">
|
||||
<div class="setting-info">
|
||||
<div class="setting-label">Odpowiedź na Twoje pytanie B2B</div>
|
||||
<div class="setting-description">E-mail gdy autor ogłoszenia odpowie na Twoje pytanie</div>
|
||||
</div>
|
||||
<label class="toggle-switch">
|
||||
<input type="checkbox" name="notify_email_classified_answer" {% if user.notify_email_classified_answer != False %}checked{% endif %}>
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="setting-item">
|
||||
<div class="setting-info">
|
||||
<div class="setting-label">Twoje ogłoszenie wygasa za 3 dni</div>
|
||||
<div class="setting-description">Przypomnienie e-mailem gdy Twoje ogłoszenie B2B zbliża się do daty wygaśnięcia (abyś mógł je przedłużyć)</div>
|
||||
</div>
|
||||
<label class="toggle-switch">
|
||||
<input type="checkbox" name="notify_email_classified_expiry" {% if user.notify_email_classified_expiry != False %}checked{% endif %}>
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-card">
|
||||
|
||||
Loading…
Reference in New Issue
Block a user