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
New email template with friendly tone and green CTA button for first-time account activation. Script with --dry-run, --test-email, --user-id flags and 72h token validity. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
221 lines
6.9 KiB
Python
221 lines
6.9 KiB
Python
"""
|
|
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 <adres>")
|
|
print("Aby wysłać do jednej osoby: --user-id <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()
|