feat: add production deployment config
- Add docker-compose.prod.yml with env var support - Add frontend/Dockerfile.prod with nginx for static serving - Fix Zulip notification to run in thread pool (avoid blocking) - Use Zulip time format for timezone-aware display - Add Zulip @mentions for users matched by email Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import asyncio
|
||||
import logging
|
||||
from uuid import UUID
|
||||
|
||||
@@ -146,7 +147,6 @@ async def sync_participant(participant_id: UUID, db: AsyncSession = Depends(get_
|
||||
async def schedule_meeting(
|
||||
data: ScheduleRequest, db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
# 1. Validate Lead Time (2 hours)
|
||||
min_start_time = datetime.now(timezone.utc) + timedelta(hours=2)
|
||||
if data.start_time.replace(tzinfo=timezone.utc) < min_start_time:
|
||||
raise HTTPException(
|
||||
@@ -154,7 +154,6 @@ async def schedule_meeting(
|
||||
detail="Meetings must be scheduled at least 2 hours in advance."
|
||||
)
|
||||
|
||||
# 2. Fetch Participants
|
||||
result = await db.execute(
|
||||
select(Participant).where(Participant.id.in_(data.participant_ids))
|
||||
)
|
||||
@@ -166,9 +165,7 @@ async def schedule_meeting(
|
||||
participant_dicts = [
|
||||
{"name": p.name, "email": p.email} for p in participants
|
||||
]
|
||||
participant_names = [p.name for p in participants]
|
||||
|
||||
# 3. Send Notifications
|
||||
email_success = await send_meeting_invite(
|
||||
participant_dicts,
|
||||
data.title,
|
||||
@@ -177,10 +174,11 @@ async def schedule_meeting(
|
||||
data.end_time
|
||||
)
|
||||
|
||||
zulip_success = send_zulip_notification(
|
||||
zulip_success = await asyncio.to_thread(
|
||||
send_zulip_notification,
|
||||
data.title,
|
||||
data.start_time,
|
||||
participant_names
|
||||
participant_dicts
|
||||
)
|
||||
|
||||
return {
|
||||
|
||||
@@ -5,13 +5,44 @@ from datetime import datetime
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_zulip_usernames_by_email(client: zulip.Client) -> dict[str, str]:
|
||||
"""Fetch all Zulip users and return a mapping of email -> full_name for mentions."""
|
||||
try:
|
||||
result = client.get_users()
|
||||
if result.get("result") == "success":
|
||||
users_map = {
|
||||
user["email"].lower(): user["full_name"]
|
||||
for user in result.get("members", [])
|
||||
if not user.get("is_bot", False)
|
||||
}
|
||||
return users_map
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to fetch Zulip users: {e}")
|
||||
return {}
|
||||
|
||||
|
||||
def format_participant_mentions(
|
||||
participants: list[dict],
|
||||
zulip_users: dict[str, str],
|
||||
) -> str:
|
||||
"""Format participants as Zulip mentions where possible, plain names otherwise."""
|
||||
formatted = []
|
||||
for p in participants:
|
||||
email = p["email"].lower()
|
||||
if email in zulip_users:
|
||||
formatted.append(f'@**{zulip_users[email]}**')
|
||||
else:
|
||||
formatted.append(p["name"])
|
||||
return ", ".join(formatted)
|
||||
|
||||
|
||||
def send_zulip_notification(
|
||||
title: str,
|
||||
start_time: datetime,
|
||||
participant_names: list[str],
|
||||
participants: list[dict],
|
||||
) -> bool:
|
||||
if not settings.zulip_site or not settings.zulip_api_key or not settings.zulip_email:
|
||||
logger.warning("Zulip credentials not configured. Skipping notification.")
|
||||
return False
|
||||
|
||||
try:
|
||||
@@ -21,13 +52,15 @@ def send_zulip_notification(
|
||||
site=settings.zulip_site
|
||||
)
|
||||
|
||||
formatted_time = start_time.strftime('%Y-%m-%d %H:%M %Z')
|
||||
people = ", ".join(participant_names)
|
||||
|
||||
zulip_users = get_zulip_usernames_by_email(client)
|
||||
people = format_participant_mentions(participants, zulip_users)
|
||||
|
||||
zulip_time = f"<time:{start_time.isoformat()}>"
|
||||
|
||||
content = (
|
||||
f"📅 **Meeting Scheduled**\n"
|
||||
f"**What:** {title}\n"
|
||||
f"**When:** {formatted_time}\n"
|
||||
f"**When:** {zulip_time}\n"
|
||||
f"**Who:** {people}"
|
||||
)
|
||||
|
||||
@@ -40,7 +73,6 @@ def send_zulip_notification(
|
||||
|
||||
result = client.send_message(request)
|
||||
if result.get("result") == "success":
|
||||
logger.info("Sent Zulip notification")
|
||||
return True
|
||||
else:
|
||||
logger.error(f"Zulip API error: {result.get('msg')}")
|
||||
|
||||
Reference in New Issue
Block a user