nordabiz/scripts/send_portal_message.py
Maciej Pienczyn 89d105b768
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
fix(scripts): use datetime.now() instead of utcnow() to match server timezone
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 17:20:48 +01:00

199 lines
6.2 KiB
Python

#!/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()