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
Rozszerzenie powiadomień o kolejne typy zdarzeń, z symetrycznymi togglami e-mail i push w /konto/prywatnosc. Migracje 103 + 104 — 6 nowych kolumn preferencji e-mail + NordaEvent.reminder_24h_sent_at. Triggery: - Forum odpowiedź → push do autora wątku (notify_push_forum_reply) - Forum cytat (> **Imię** napisał(a):) → push + email do cytowanego (notify_push/email_forum_quote) - Admin publikuje aktualność → broadcast push (ON) + email (OFF) do aktywnych członków (notify_push/email_announcements) - Board: utworzenie / publikacja programu / publikacja protokołu → broadcast push + opt-in email (notify_push/email_board_meetings) - Nowe wydarzenie w kalendarzu → broadcast push + email (oba ON) (notify_push/email_event_invites) - Cron scripts/event_reminders_cron.py co godzinę — wydarzenia za 23-25h, dla zapisanych (EventAttendee.status != 'declined') push + email, znacznik NordaEvent.reminder_24h_sent_at żeby nie dublować. Email defaults dobrane, by nie zalać inbox: broadcast OFF (announcements, board, forum_reply), personalne/actionable ON (forum_quote, event_invites, event_reminders). Wszystkie nowe e-maile mają jednym-kliknięciem unsubscribe (RFC 8058 + link w stopce) — unsubscribe_tokens.py rozszerzony o nowe typy. Cron entry do dodania na prod (osobny krok, bo to edycja crontaba): 0 * * * * cd /var/www/nordabiznes && venv/bin/python3 scripts/event_reminders_cron.py Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
136 lines
5.7 KiB
Python
Executable File
136 lines
5.7 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Event reminders cron — wysyła przypomnienia push/email 24h przed wydarzeniem.
|
|
|
|
Uruchomienie (cron co godzinę):
|
|
0 * * * * cd /var/www/nordabiznes && venv/bin/python3 scripts/event_reminders_cron.py
|
|
|
|
Logika:
|
|
- Szuka wydarzeń, których event_date+time_start mieści się między now()+23h a now()+25h
|
|
- Które jeszcze nie mają reminder_24h_sent_at ustawionego
|
|
- Dla każdego: pobiera zapisanych (EventAttendee.status != 'declined')
|
|
- Dla każdego zapisanego: sprawdza notify_push_event_reminders i notify_email_event_reminders
|
|
- Wysyła push i/lub email
|
|
- Oznacza event.reminder_24h_sent_at = now()
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import logging
|
|
from datetime import datetime, timedelta, time
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
from dotenv import load_dotenv
|
|
load_dotenv()
|
|
|
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s] %(message)s')
|
|
logger = logging.getLogger(__name__)
|
|
|
|
from database import SessionLocal, NordaEvent, EventAttendee, User
|
|
|
|
|
|
def _combine_event_datetime(event):
|
|
"""Zwraca datetime startu wydarzenia (data + time_start lub 09:00 jeśli brak)."""
|
|
t = event.time_start if event.time_start else time(9, 0)
|
|
return datetime.combine(event.event_date, t)
|
|
|
|
|
|
def main():
|
|
from email_service import init_email_service
|
|
init_email_service()
|
|
from blueprints.push.push_service import send_push
|
|
from email_service import send_email, _email_v3_wrap
|
|
|
|
now = datetime.now()
|
|
window_start = now + timedelta(hours=23)
|
|
window_end = now + timedelta(hours=25)
|
|
|
|
db = SessionLocal()
|
|
try:
|
|
candidates = db.query(NordaEvent).filter(
|
|
NordaEvent.event_date >= window_start.date(),
|
|
NordaEvent.event_date <= window_end.date(),
|
|
NordaEvent.reminder_24h_sent_at.is_(None),
|
|
).all()
|
|
|
|
fired = 0
|
|
for event in candidates:
|
|
event_dt = _combine_event_datetime(event)
|
|
if not (window_start <= event_dt <= window_end):
|
|
continue
|
|
|
|
attendees = db.query(EventAttendee).filter(
|
|
EventAttendee.event_id == event.id,
|
|
EventAttendee.status != 'declined',
|
|
).all()
|
|
|
|
if not attendees:
|
|
event.reminder_24h_sent_at = now
|
|
continue
|
|
|
|
time_str = event.time_start.strftime('%H:%M') if event.time_start else ''
|
|
date_str = event.event_date.strftime('%d.%m.%Y')
|
|
url_path = f'/kalendarz/{event.id}'
|
|
title = f'Jutro wydarzenie: {event.title[:60]}'
|
|
body = f'{date_str} {time_str} · {event.location or "Miejsce w szczegółach"}'
|
|
|
|
push_count = 0
|
|
email_count = 0
|
|
for a in attendees:
|
|
user = db.query(User).filter(User.id == a.user_id).first()
|
|
if not user or not user.is_active:
|
|
continue
|
|
try:
|
|
if getattr(user, 'notify_push_event_reminders', True) is not False:
|
|
send_push(
|
|
user_id=user.id,
|
|
title=title,
|
|
body=body,
|
|
url=url_path,
|
|
tag=f'event-reminder-{event.id}',
|
|
)
|
|
push_count += 1
|
|
except Exception as e:
|
|
logger.warning(f"push err user={user.id}: {e}")
|
|
|
|
try:
|
|
if user.email and getattr(user, 'notify_email_event_reminders', True) is not False:
|
|
subject = f"Przypomnienie: {event.title[:60]} jutro o {time_str}"
|
|
body_text = f"Przypominamy o wydarzeniu na które jesteś zapisany:\n\n{event.title}\nTermin: {date_str} {time_str}\nMiejsce: {event.location or '-'}\n\nSzczegóły: https://nordabiznes.pl{url_path}"
|
|
content = (
|
|
f'<p style="margin:0 0 16px;color:#1e293b;font-size:16px;">Cześć <strong>{user.name or user.email}</strong>!</p>'
|
|
f'<p style="margin:0 0 20px;color:#475569;font-size:15px;">Przypominamy o wydarzeniu na które jesteś zapisany:</p>'
|
|
f'<h3 style="color:#1e3a8a;">{event.title}</h3>'
|
|
f'<p style="color:#475569;line-height:1.6">Termin: <b>{date_str} {time_str}</b><br>'
|
|
f'Miejsce: {event.location or "-"}</p>'
|
|
f'<p style="margin:24px 0;"><a href="https://nordabiznes.pl{url_path}" '
|
|
f'style="background:#1e3a8a;color:#fff;padding:12px 24px;border-radius:6px;text-decoration:none">Zobacz szczegóły</a></p>'
|
|
)
|
|
body_html = _email_v3_wrap('Przypomnienie o wydarzeniu', 'Norda Biznes Partner', content)
|
|
send_email(
|
|
to=[user.email],
|
|
subject=subject,
|
|
body_text=body_text,
|
|
body_html=body_html,
|
|
email_type='event_reminder',
|
|
user_id=user.id,
|
|
recipient_name=user.name,
|
|
notification_type='event_reminders',
|
|
)
|
|
email_count += 1
|
|
except Exception as e:
|
|
logger.warning(f"email err user={user.id}: {e}")
|
|
|
|
event.reminder_24h_sent_at = now
|
|
fired += 1
|
|
logger.info(f"event={event.id} '{event.title}' — push={push_count}, email={email_count}")
|
|
|
|
db.commit()
|
|
logger.info(f"Done. Fired reminders for {fired} event(s).")
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|