#!/usr/bin/env python3 """ Send Portal Message ==================== Send a message through the NordaBiznes conversation system using the application layer. Creates conversation if needed (with 1:1 dedup), sends message, triggers email notification. Usage: DATABASE_URL=... python3 scripts/send_portal_message.py \ --from-user-id 1 \ --to-user-id 38 \ --message "Treść wiadomości" # Or with HTML content from file: DATABASE_URL=... python3 scripts/send_portal_message.py \ --from-user-id 1 \ --to-user-id 38 \ --message-file message.html """ import os import sys import argparse from datetime import datetime sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from database import ( SessionLocal, User, Conversation, ConversationMember, ConvMessage, ) def find_or_create_conversation(db, from_user_id, to_user_id): """Find existing 1:1 conversation or create a new one.""" # Look for existing 1:1 conversation between these two users from_convs = db.query(ConversationMember.conversation_id).filter_by(user_id=from_user_id).subquery() to_convs = db.query(ConversationMember.conversation_id).filter_by(user_id=to_user_id).subquery() existing = db.query(Conversation).filter( Conversation.id.in_(db.query(from_convs.c.conversation_id)), Conversation.id.in_(db.query(to_convs.c.conversation_id)), Conversation.is_group == False, ).first() if existing: print(f" Existing conversation found: ID {existing.id}") return existing # Create new conversation conv = Conversation( is_group=False, owner_id=from_user_id, created_at=datetime.now(), updated_at=datetime.now(), ) db.add(conv) db.flush() db.add(ConversationMember( conversation_id=conv.id, user_id=from_user_id, role='owner', joined_at=datetime.now(), )) db.add(ConversationMember( conversation_id=conv.id, user_id=to_user_id, role='member', joined_at=datetime.now(), )) db.flush() print(f" New conversation created: ID {conv.id}") return conv def send_message(db, conv, sender_id, content): """Send a message in a conversation.""" msg = ConvMessage( conversation_id=conv.id, sender_id=sender_id, content=content, created_at=datetime.now(), ) db.add(msg) db.flush() conv.last_message_id = msg.id conv.updated_at = msg.created_at print(f" Message sent: ID {msg.id}, {len(content)} chars") return msg def send_email_notification(db, sender, recipient, conv, msg): """Send email notification to recipient.""" try: from email_service import send_email, build_message_notification_email import re if not recipient.email: print(f" Email: skipped (no email for {recipient.name})") return # Check if user has email notifications enabled if hasattr(recipient, 'notify_email_messages') and recipient.notify_email_messages == False: print(f" Email: skipped (notifications disabled for {recipient.name})") return sender_name = sender.name or sender.email.split('@')[0] preview = re.sub(r'<[^>]+>', '', msg.content or '').strip()[:200] subject_line = f'Nowa wiadomość od {sender_name} — Norda Biznes' # Build URL message_url = f'https://nordabiznes.pl/wiadomosci?conv={conv.id}' settings_url = 'https://nordabiznes.pl/konto/prywatnosc' email_html, email_text = build_message_notification_email( sender_name=sender_name, subject='Nowa wiadomość', content_preview=preview, message_url=message_url, settings_url=settings_url, ) send_email( to=[recipient.email], subject=subject_line, body_text=email_text, body_html=email_html, email_type='message_notification', user_id=recipient.id, recipient_name=recipient.name, ) print(f" Email: sent to {recipient.email}") except Exception as e: print(f" Email: failed ({e})") def main(): parser = argparse.ArgumentParser(description='Send portal message') parser.add_argument('--from-user-id', type=int, required=True, help='Sender user ID') parser.add_argument('--to-user-id', type=int, required=True, help='Recipient user ID') parser.add_argument('--message', type=str, help='Message content (HTML)') parser.add_argument('--message-file', type=str, help='File with message content (HTML)') parser.add_argument('--no-email', action='store_true', help='Skip email notification') parser.add_argument('--dry-run', action='store_true', help='Show what would be done') args = parser.parse_args() if not args.message and not args.message_file: parser.error('Provide --message or --message-file') content = args.message if args.message_file: with open(args.message_file) as f: content = f.read() db = SessionLocal() try: sender = db.query(User).get(args.from_user_id) recipient = db.query(User).get(args.to_user_id) if not sender: print(f"ERROR: Sender user ID {args.from_user_id} not found") sys.exit(1) if not recipient: print(f"ERROR: Recipient user ID {args.to_user_id} not found") sys.exit(1) print(f"From: {sender.name} ({sender.email})") print(f"To: {recipient.name} ({recipient.email})") print(f"Message: {len(content)} chars") if args.dry_run: print("\n=== DRY RUN — no changes made ===") return conv = find_or_create_conversation(db, args.from_user_id, args.to_user_id) msg = send_message(db, conv, args.from_user_id, content) db.commit() if not args.no_email: send_email_notification(db, sender, recipient, conv, msg) print(f"\nDone. Conversation ID: {conv.id}, Message ID: {msg.id}") except Exception as e: db.rollback() print(f"ERROR: {e}") sys.exit(1) finally: db.close() if __name__ == '__main__': main()