- Fork elmeg-demo codebase for multi-band support - Add data importer infrastructure with base class - Create band-specific importers: - phish.py: Phish.net API v5 - grateful_dead.py: Grateful Stats API - setlistfm.py: Dead & Company, Billy Strings (Setlist.fm) - Add spec-kit configuration for Gemini - Update README with supported bands and architecture
220 lines
8.3 KiB
Python
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()
|