diff --git a/email_service.py b/email_service.py index bf98408..38ecca7 100644 --- a/email_service.py +++ b/email_service.py @@ -441,6 +441,83 @@ https://nordabiznes.pl ) +def send_welcome_activation_email(email: str, name: str, reset_url: str) -> bool: + """Send welcome activation email to users who never received credentials.""" + subject = "Witamy w Norda Biznes Partner — Ustaw hasło do konta" + + body_text = f"""Witaj {name}! + +Twoje konto w portalu Norda Biznes Partner jest gotowe. + +Aby ustawić hasło i zalogować się, kliknij w poniższy link: +{reset_url} + +Link jest ważny przez 72 godziny. + +Co znajdziesz w portalu: +- Katalog firm członkowskich Izby +- Forum dyskusyjne dla członków +- Wiadomości prywatne i networking +- Asystent AI do wyszukiwania usług + +Pozdrawiamy, +Zespół Norda Biznes Partner +https://nordabiznes.pl +""" + + check = ( + '
' + '
' + ) + + content = f''' +

Witaj {name}!

+

Twoje konto w portalu Norda Biznes Partner jest gotowe. Kliknij poniższy przycisk, aby ustawić hasło i zalogować się po raz pierwszy.

+ + + +
+ Ustaw hasło i zaloguj się +
+ + + +
+

Link jest ważny przez 72 godziny. Po tym czasie możesz poprosić o nowy link na stronie logowania.

+
+ +

Co znajdziesz w portalu:

+ + + + + +
+ {check}Katalog firm członkowskich Izby +
+ {check}Forum dyskusyjne dla członków +
+ {check}Wiadomości prywatne i networking +
+ {check}Asystent AI do wyszukiwania usług +
+ +

Jeśli przycisk nie działa, skopiuj i wklej ten link:

+

{reset_url}

''' + + body_html = _email_v3_wrap('Witamy w portalu!', 'Norda Biznes Partner', content) + + return send_email( + to=[email], + subject=subject, + body_text=body_text, + body_html=body_html, + email_type='welcome_activation', + recipient_name=name + ) + + def send_welcome_email(email: str, name: str, verification_url: str) -> bool: """Send welcome/verification email after registration.""" subject = "Witamy w Norda Biznes Partner — Potwierdź email" diff --git a/scripts/send_welcome_activation.py b/scripts/send_welcome_activation.py new file mode 100644 index 0000000..a1c3ccb --- /dev/null +++ b/scripts/send_welcome_activation.py @@ -0,0 +1,220 @@ +""" +Send welcome activation emails to users who never logged in. + +These users were imported (batch import, admin panel, WhatsApp registration) +with passwords but never received welcome emails or password reset links. +This script generates reset tokens (72h validity) and sends a welcoming +activation email with a link to set their password. + +Usage: + # Preview who would receive emails (no sending) + python3 send_welcome_activation.py --dry-run + + # Send test email to admin + python3 send_welcome_activation.py --test-email maciej.pienczyn@inpi.pl + + # Send to a specific user by ID + python3 send_welcome_activation.py --user-id 53 + + # Send to all never-logged-in users (requires confirmation) + python3 send_welcome_activation.py --send-all + +Run on production server with DATABASE_URL set. +""" + +import os +import sys +import argparse +import secrets +from datetime import datetime, timedelta + +# Add project root to path +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from database import SessionLocal, User +import email_service + +TOKEN_VALIDITY_HOURS = 72 +BASE_URL = 'https://nordabiznes.pl' + + +def get_never_logged_in_users(db): + """Find all active, verified users who never logged in.""" + return ( + db.query(User) + .filter( + User.last_login.is_(None), + User.is_active.is_(True), + ) + .order_by(User.id) + .all() + ) + + +def send_activation_email(db, user): + """Generate token and send welcome activation email to a user.""" + token = secrets.token_urlsafe(32) + expires = datetime.now() + timedelta(hours=TOKEN_VALIDITY_HOURS) + + user.reset_token = token + user.reset_token_expires = expires + db.commit() + + reset_url = f"{BASE_URL}/reset-password/{token}" + + success = email_service.send_welcome_activation_email( + email=user.email, + name=user.name, + reset_url=reset_url + ) + return success + + +def cmd_dry_run(db): + """Show users who would receive activation emails.""" + users = get_never_logged_in_users(db) + + if not users: + print("Brak użytkowników do aktywacji (wszyscy się zalogowali).") + return + + print(f"Użytkownicy, którzy nigdy się nie zalogowali ({len(users)}):\n") + print(f"{'ID':>4} {'Imię i nazwisko':<30} {'Email':<40} {'Aktywny':>7} {'Zweryfikowany':>13}") + print("-" * 100) + + for u in users: + print(f"{u.id:>4} {u.name:<30} {u.email:<40} {'tak' if u.is_active else 'nie':>7} {'tak' if u.is_verified else 'nie':>13}") + + print(f"\nRazem: {len(users)} użytkowników") + print("\nAby wysłać e-mail testowy: --test-email ") + print("Aby wysłać do jednej osoby: --user-id ") + + +def cmd_test_email(db, test_email): + """Send a test activation email to specified address.""" + # Find or create a fake context for the test + print(f"Wysyłanie testowego e-maila powitalnego do: {test_email}") + + token = secrets.token_urlsafe(32) + reset_url = f"{BASE_URL}/reset-password/{token}" + + # Use test data + success = email_service.send_welcome_activation_email( + email=test_email, + name="Testowy Użytkownik", + reset_url=reset_url + ) + + if success: + print(f" OK: E-mail testowy wysłany do {test_email}") + print(f" Link (nieaktywny - testowy token): {reset_url}") + else: + print(f" FAIL: Nie udało się wysłać e-maila do {test_email}") + sys.exit(1) + + +def cmd_send_user(db, user_id): + """Send activation email to a specific user.""" + user = db.query(User).get(user_id) + + if not user: + print(f"ERROR: Nie znaleziono użytkownika o ID {user_id}") + sys.exit(1) + + if not user.is_active: + print(f"ERROR: Użytkownik {user.name} (ID {user_id}) jest nieaktywny") + sys.exit(1) + + if user.last_login is not None: + print(f"SKIP: Użytkownik {user.name} (ID {user_id}) już się logował ({user.last_login})") + return + + print(f"Wysyłanie e-maila powitalnego do: {user.name} <{user.email}> (ID {user_id})") + + success = send_activation_email(db, user) + + if success: + print(f" OK: E-mail wysłany do {user.name} <{user.email}>") + else: + print(f" FAIL: Nie udało się wysłać e-maila do {user.name} <{user.email}>") + sys.exit(1) + + +def cmd_send_all(db): + """Send activation emails to all never-logged-in users (with confirmation).""" + users = get_never_logged_in_users(db) + + if not users: + print("Brak użytkowników do aktywacji.") + return + + print(f"Wysyłka do {len(users)} użytkowników:\n") + for u in users: + print(f" {u.id:>4} {u.name:<30} {u.email}") + + confirm = input(f"\nCzy wysłać e-maile do {len(users)} użytkowników? (tak/nie): ") + if confirm.strip().lower() != 'tak': + print("Anulowano.") + return + + results = {'sent': [], 'failed': []} + + for user in users: + try: + success = send_activation_email(db, user) + if success: + results['sent'].append(f"{user.name} <{user.email}>") + print(f" OK: {user.name} <{user.email}>") + else: + results['failed'].append(f"{user.name} <{user.email}>") + print(f" FAIL: {user.name} <{user.email}>") + except Exception as e: + results['failed'].append(f"{user.name} <{user.email}>: {e}") + print(f" ERROR: {user.name} <{user.email}>: {e}") + + print(f"\n{'='*50}") + print(f"Wysłano: {len(results['sent'])}") + print(f"Błędy: {len(results['failed'])}") + + if results['failed']: + print("\nSzczegóły błędów:") + for f in results['failed']: + print(f" - {f}") + + +def main(): + parser = argparse.ArgumentParser( + description='Wyślij e-maile powitalne do użytkowników, którzy nigdy się nie zalogowali.' + ) + group = parser.add_mutually_exclusive_group(required=True) + group.add_argument('--dry-run', action='store_true', + help='Pokaż listę użytkowników bez wysyłania e-maili') + group.add_argument('--test-email', type=str, + help='Wyślij testowy e-mail na podany adres') + group.add_argument('--user-id', type=int, + help='Wyślij e-mail do użytkownika o podanym ID') + group.add_argument('--send-all', action='store_true', + help='Wyślij e-maile do wszystkich niezalogowanych (wymaga potwierdzenia)') + + args = parser.parse_args() + + if not args.dry_run and not email_service.is_configured(): + print("ERROR: Email service not configured. Set Azure credentials in .env") + sys.exit(1) + + db = SessionLocal() + try: + if args.dry_run: + cmd_dry_run(db) + elif args.test_email: + cmd_test_email(db, args.test_email) + elif args.user_id: + cmd_send_user(db, args.user_id) + elif args.send_all: + cmd_send_all(db) + finally: + db.close() + + +if __name__ == '__main__': + main()