elmeg-demo/backend/services/weekly_digest.py
2025-12-26 22:50:09 -08:00

220 lines
8.3 KiB
Python

"""
Weekly Digest Email Service
Sends weekly digest emails to users who have email_digest enabled.
Run via cron job: 0 9 * * 0 (Sunday at 9am)
"""
import os
import sys
# Add backend to path
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from datetime import datetime, timedelta
from sqlmodel import Session, select, func
from database import engine
from models import User, Show, Performance, Review, Rating, ChaseSong, Attendance
def get_weekly_stats(session: Session, user_id: int, start_date: datetime, end_date: datetime):
"""Get user's activity stats for the week"""
# Shows attended this week
shows_attended = session.exec(
select(func.count(Attendance.id))
.where(Attendance.user_id == user_id)
.where(Attendance.created_at >= start_date)
.where(Attendance.created_at < end_date)
).one() or 0
# Reviews written this week
reviews_written = session.exec(
select(func.count(Review.id))
.where(Review.user_id == user_id)
.where(Review.created_at >= start_date)
.where(Review.created_at < end_date)
).one() or 0
# Ratings given this week
ratings_given = session.exec(
select(func.count(Rating.id))
.where(Rating.user_id == user_id)
.where(Rating.created_at >= start_date)
.where(Rating.created_at < end_date)
).one() or 0
# Check for caught chase songs
chase_caught = session.exec(
select(func.count(ChaseSong.id))
.where(ChaseSong.user_id == user_id)
.where(ChaseSong.caught_at >= start_date)
.where(ChaseSong.caught_at < end_date)
).one() or 0
return {
"shows_attended": shows_attended,
"reviews_written": reviews_written,
"ratings_given": ratings_given,
"chase_caught": chase_caught
}
def get_community_highlights(session: Session, start_date: datetime, end_date: datetime):
"""Get community highlights for the week"""
# New shows added
new_shows = session.exec(
select(func.count(Show.id))
.where(Show.date >= start_date.date())
.where(Show.date < end_date.date())
).one() or 0
# Total reviews this week
total_reviews = session.exec(
select(func.count(Review.id))
.where(Review.created_at >= start_date)
.where(Review.created_at < end_date)
).one() or 0
# Most reviewed performance this week
top_perf = session.exec(
select(Performance.id, func.count(Review.id).label("count"))
.join(Review, Review.performance_id == Performance.id)
.where(Review.created_at >= start_date)
.where(Review.created_at < end_date)
.group_by(Performance.id)
.order_by(func.count(Review.id).desc())
.limit(1)
).first()
return {
"new_shows": new_shows,
"total_reviews": total_reviews,
"top_performance_id": top_perf[0] if top_perf else None
}
def send_digest_email(user, stats: dict, highlights: dict, frontend_url: str):
"""Send the weekly digest email to a user"""
from services.email_service import email_service
subject = "Your Weekly Elmeg Digest"
# Build activity section
activity_items = []
if stats["shows_attended"] > 0:
activity_items.append(f"🎵 Attended {stats['shows_attended']} show{'s' if stats['shows_attended'] > 1 else ''}")
if stats["reviews_written"] > 0:
activity_items.append(f"✍️ Wrote {stats['reviews_written']} review{'s' if stats['reviews_written'] > 1 else ''}")
if stats["ratings_given"] > 0:
activity_items.append(f"⭐ Gave {stats['ratings_given']} rating{'s' if stats['ratings_given'] > 1 else ''}")
if stats["chase_caught"] > 0:
activity_items.append(f"🎯 Caught {stats['chase_caught']} chase song{'s' if stats['chase_caught'] > 1 else ''}!")
activity_html = ""
if activity_items:
activity_html = "<ul>" + "".join([f"<li>{item}</li>" for item in activity_items]) + "</ul>"
else:
activity_html = "<p style='color: #666;'>No activity this week. Get out there and catch some shows!</p>"
html_content = f"""
<html>
<body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; background-color: #f5f5f5; padding: 20px;">
<div style="max-width: 600px; margin: 0 auto; background: white; border-radius: 12px; padding: 40px; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">
<h2 style="color: #2563eb; margin-top: 0;">Weekly Digest</h2>
<h3 style="color: #374151; border-bottom: 2px solid #e5e7eb; padding-bottom: 8px;">Your Activity</h3>
{activity_html}
<h3 style="color: #374151; border-bottom: 2px solid #e5e7eb; padding-bottom: 8px; margin-top: 30px;">Community Highlights</h3>
<ul>
<li>🆕 {highlights['new_shows']} new show{'s' if highlights['new_shows'] != 1 else ''} added</li>
<li>📝 {highlights['total_reviews']} review{'s' if highlights['total_reviews'] != 1 else ''} written by the community</li>
</ul>
<div style="margin: 30px 0; text-align: center;">
<a href="{frontend_url}" style="background: linear-gradient(135deg, #2563eb, #4f46e5); color: white; padding: 14px 32px; text-decoration: none; border-radius: 8px; font-weight: 600; display: inline-block;">Visit Elmeg</a>
</div>
<hr style="border: 0; border-top: 1px solid #eee; margin: 30px 0;">
<p style="font-size: 12px; color: #999; margin-bottom: 0;">You're receiving this because you enabled weekly digests. <a href="{frontend_url}/settings" style="color: #2563eb;">Manage preferences</a></p>
</div>
</body>
</html>
"""
text_content = f"""
Weekly Elmeg Digest
YOUR ACTIVITY:
- Attended {stats['shows_attended']} shows
- Wrote {stats['reviews_written']} reviews
- Gave {stats['ratings_given']} ratings
- Caught {stats['chase_caught']} chase songs
COMMUNITY HIGHLIGHTS:
- {highlights['new_shows']} new shows added
- {highlights['total_reviews']} reviews written
Visit Elmeg: {frontend_url}
To disable weekly digests, visit {frontend_url}/settings
"""
return email_service.send_email(user.email, subject, html_content, text_content)
def send_weekly_digests():
"""Main function to send all weekly digest emails"""
print("=" * 60)
print("WEEKLY DIGEST EMAIL SENDER")
print("=" * 60)
# Calculate date range (last 7 days)
end_date = datetime.utcnow()
start_date = end_date - timedelta(days=7)
print(f"Period: {start_date.date()} to {end_date.date()}")
from services.email_service import email_service
frontend_url = email_service.frontend_url
with Session(engine) as session:
# Get community highlights (shared across all emails)
highlights = get_community_highlights(session, start_date, end_date)
print(f"Community stats: {highlights['new_shows']} shows, {highlights['total_reviews']} reviews")
# Find all users with email_digest enabled
users_with_digest = session.exec(
select(User)
.where(User.is_active == True)
.where(User.email_verified == True)
).all()
sent_count = 0
skipped_count = 0
for user in users_with_digest:
# Check if user has digest enabled
if not user.preferences or not user.preferences.email_digest:
skipped_count += 1
continue
# Get user's stats
stats = get_weekly_stats(session, user.id, start_date, end_date)
# Send the digest
try:
success = send_digest_email(user, stats, highlights, frontend_url)
if success:
sent_count += 1
print(f" ✓ Sent to {user.email}")
else:
print(f" ✗ Failed for {user.email}")
except Exception as e:
print(f" ✗ Error for {user.email}: {e}")
print(f"\n✓ Sent {sent_count} digest emails, skipped {skipped_count}")
if __name__ == "__main__":
send_weekly_digests()