Files
prayer-app/src/app/api/send-scheduled-emails/route.ts
T
nmemmert d9784738e4 feat: Add prayer chains, social sharing, and testimony wall
- Add PrayerChain component for visualizing linked prayers
- Add SharePrayer component with social media integration (Twitter, Facebook, WhatsApp, copy link)
- Create /testimonies page for public answered prayer stories
- Update Firestore rules for public testimony access
- Enhance Organization tab with prayer chains
- Add Testimonies navigation tab
- Fix Firebase Admin SDK integration for server-side operations
- Improve email system with proper server-side authentication
- Update prayer form to handle optional fields correctly
- Deploy updated Firestore security rules
2025-12-17 13:50:21 -05:00

452 lines
14 KiB
TypeScript

import { NextRequest, NextResponse } from 'next/server';
import { admin } from '@/lib/firebase-admin';
import nodemailer from 'nodemailer';
function generatePrayerEmailHTML(message: string, subject: string) {
// Parse the message to extract prayer details and stats
const lines = message.split('\n');
const stats = { total: 0, active: 0, answered: 0, archived: 0 };
const activePrayers = [];
const answeredPrayers = [];
let inActiveSection = false;
let inAnsweredSection = false;
for (const line of lines) {
if (line.includes('Total Prayers:')) {
stats.total = parseInt(line.split(':')[1].trim()) || 0;
} else if (line.includes('Active Prayers:')) {
stats.active = parseInt(line.split(':')[1].trim()) || 0;
} else if (line.includes('Answered Prayers:')) {
stats.answered = parseInt(line.split(':')[1].trim()) || 0;
} else if (line.includes('Archived Prayers:')) {
stats.archived = parseInt(line.split(':')[1].trim()) || 0;
} else if (line.includes('--- Your Active Prayers ---')) {
inActiveSection = true;
inAnsweredSection = false;
} else if (line.includes('--- Recently Answered Prayers ---')) {
inActiveSection = false;
inAnsweredSection = true;
} else if (inActiveSection && line.includes(' - ')) {
const match = line.match(/(\d{4}-\d{2}-\d{2}) - (PRAYER|PRAISE): (.+)/);
if (match) {
activePrayers.push({
date: match[1],
type: match[2].toLowerCase(),
text: match[3]
});
}
} else if (inAnsweredSection && line.includes(' - ')) {
const match = line.match(/(\d{4}-\d{2}-\d{2}) - (PRAYER|PRAISE): (.+)/);
if (match) {
answeredPrayers.push({
date: match[1],
type: match[2].toLowerCase(),
text: match[3]
});
}
}
}
const html = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${subject}</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: #333;
max-width: 600px;
margin: 0 auto;
background-color: #f8f9fa;
padding: 20px;
}
.container {
background-color: white;
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 30px 20px;
text-align: center;
}
.header h1 {
margin: 0;
font-size: 24px;
font-weight: 600;
}
.header .subtitle {
margin: 8px 0 0 0;
opacity: 0.9;
font-size: 16px;
}
.content {
padding: 30px 20px;
}
.prayer-card {
background: #f8f9fa;
border-left: 4px solid #667eea;
padding: 20px;
margin: 20px 0;
border-radius: 8px;
}
.prayer-type {
font-weight: 600;
color: #667eea;
text-transform: uppercase;
font-size: 14px;
letter-spacing: 0.5px;
}
.prayer-date {
color: #666;
font-size: 14px;
margin-bottom: 10px;
}
.prayer-text {
font-size: 16px;
line-height: 1.6;
margin: 10px 0;
}
.prayer-journal {
background: #fff;
border-left: 3px solid #764ba2;
padding: 15px;
margin: 15px 0;
font-style: italic;
color: #555;
}
.pray-for {
background: #e8f4fd;
padding: 10px 15px;
border-radius: 6px;
margin: 10px 0;
font-weight: 500;
color: #2c5282;
}
.summary-section {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
color: white;
padding: 20px;
margin: 20px 0;
border-radius: 8px;
}
.summary-section h3 {
margin: 0 0 15px 0;
font-size: 18px;
}
.summary-item {
background: rgba(255, 255, 255, 0.1);
padding: 15px;
margin: 10px 0;
border-radius: 6px;
}
.summary-item .date {
color: #ffed4e;
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
}
.summary-item .prayer-type {
color: #ffed4e;
font-size: 12px;
margin-left: 10px;
}
.footer {
background: #f8f9fa;
padding: 20px;
text-align: center;
color: #666;
font-size: 14px;
}
.verse {
background: #667eea;
color: white;
padding: 20px;
border-radius: 8px;
margin: 20px 0;
text-align: center;
}
.verse blockquote {
font-style: italic;
font-size: 16px;
margin: 0 0 10px 0;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 15px;
margin: 25px 0;
}
.stat-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 20px;
border-radius: 12px;
text-align: center;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.stat-number {
font-size: 28px;
font-weight: bold;
margin-bottom: 5px;
}
.stat-label {
font-size: 14px;
opacity: 0.9;
}
.section {
margin: 30px 0;
}
.section h3 {
color: #333;
margin-bottom: 20px;
font-size: 20px;
}
.celebration {
text-align: center;
padding: 40px 20px;
background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%);
border-radius: 12px;
margin: 30px 0;
}
.celebration h3 {
color: #333;
margin-bottom: 10px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🙏 Prayer App</h1>
<div class="subtitle">${subject}</div>
</div>
<div class="content">
<!-- Prayer Statistics -->
<div class="stats-grid">
<div class="stat-card">
<div class="stat-number">${stats.total}</div>
<div class="stat-label">Total Prayers</div>
</div>
<div class="stat-card">
<div class="stat-number">${stats.active}</div>
<div class="stat-label">Active</div>
</div>
<div class="stat-card">
<div class="stat-number">${stats.answered}</div>
<div class="stat-label">Answered</div>
</div>
<div class="stat-card">
<div class="stat-number">${stats.archived}</div>
<div class="stat-label">Archived</div>
</div>
</div>
${activePrayers.length > 0 ? `
<div class="section">
<h3>📝 Your Active Prayers</h3>
${activePrayers.map(prayer => `
<div class="prayer-card">
<div class="prayer-type">${prayer.type}</div>
<div class="prayer-date">${new Date(prayer.date).toLocaleDateString()}</div>
<div class="prayer-text">${prayer.text}</div>
</div>
`).join('')}
</div>
` : `
<div class="celebration">
<h3>🎉 All Caught Up!</h3>
<p>You have no active prayers. Great job staying on top of your prayer life!</p>
</div>
`}
${answeredPrayers.length > 0 ? `
<div class="section">
<h3>🙏 Recently Answered Prayers</h3>
${answeredPrayers.map(prayer => `
<div class="prayer-card answered">
<div class="prayer-type">${prayer.type}</div>
<div class="prayer-date">${new Date(prayer.date).toLocaleDateString()}</div>
<div class="prayer-text">${prayer.text}</div>
</div>
`).join('')}
</div>
` : ''}
</div>
<div class="verse">
<blockquote>"Pray without ceasing"</blockquote>
<cite>— 1 Thessalonians 5:17</cite>
</div>
</div>
<div class="footer">
<p>Sent with ❤️ from your Prayer App</p>
<p>This email helps you stay connected to your spiritual journey.</p>
</div>
</div>
</body>
</html>`;
return html;
}
export async function GET() {
try {
console.log('🔄 Checking for scheduled emails...');
// Get SMTP config using Firebase Admin SDK
const configDoc = await admin.firestore().collection('config').doc('smtp').get();
if (!configDoc.exists) {
console.log('❌ SMTP configuration not found');
return NextResponse.json({ message: 'SMTP configuration not found' }, { status: 500 });
}
const smtpConfig = configDoc.data();
// Create transporter
const transporter = nodemailer.createTransport({
host: smtpConfig.host,
port: smtpConfig.port,
secure: smtpConfig.secure,
auth: {
user: smtpConfig.user,
pass: smtpConfig.pass,
},
});
// Get all users (send daily emails to everyone) using Firebase Admin SDK
const usersSnapshot = await admin.firestore().collection('users').get();
let emailsSent = 0;
for (const userDoc of usersSnapshot.docs) {
const userData = userDoc.data();
const userId = userDoc.id;
// Skip users without email
if (!userData.email) {
continue;
}
// Check last email sent time - send daily
const lastEmailSent = userData.lastEmailSent ? new Date(userData.lastEmailSent.toDate()) : new Date(0);
const now = new Date();
// Send if more than 24 hours have passed
const shouldSendEmail = (now.getTime() - lastEmailSent.getTime()) > (24 * 60 * 60 * 1000);
if (!shouldSendEmail) {
continue;
}
// Get user's prayers for stats using Firebase Admin SDK
const prayersSnapshot = await admin.firestore().collection('prayers').where('userId', '==', userId).get();
const userPrayers = prayersSnapshot.docs.map(doc => doc.data());
// Calculate stats
const activePrayers = userPrayers.filter(p => !p.archived);
const archivedPrayers = userPrayers.filter(p => p.archived);
const answeredPrayers = userPrayers.filter(p => p.answered);
const totalPrayers = userPrayers.length;
// Build email content with stats
let message = `--- Daily Prayer Summary ---\n\n`;
message += `📊 Your Prayer Statistics:\n`;
message += `• Total Prayers: ${totalPrayers}\n`;
message += `• Active Prayers: ${activePrayers.length}\n`;
message += `• Answered Prayers: ${answeredPrayers.length}\n`;
message += `• Archived Prayers: ${archivedPrayers.length}\n\n`;
if (activePrayers.length > 0) {
message += `--- Your Active Prayers ---\n`;
activePrayers.forEach(prayer => {
message += `\n${prayer.date} - ${prayer.type.toUpperCase()}: ${prayer.text}`;
if (prayer.prayFor) {
message += ` (Praying for: ${prayer.prayFor})`;
}
if (prayer.journal) {
message += `\n Journal: ${prayer.journal}`;
}
message += `\n`;
});
} else {
message += `🎉 Great job! You have no active prayers - all caught up!\n\n`;
}
if (answeredPrayers.length > 0) {
message += `\n--- Recently Answered Prayers ---\n`;
// Show answered prayers from the last 7 days
const recentAnswered = answeredPrayers.filter(p => {
const answeredDate = p.answeredDate ? new Date(p.answeredDate) : new Date(p.date);
const weekAgo = new Date();
weekAgo.setDate(weekAgo.getDate() - 7);
return answeredDate >= weekAgo;
});
if (recentAnswered.length > 0) {
recentAnswered.forEach(prayer => {
message += `\n${prayer.date} - ${prayer.type.toUpperCase()}: ${prayer.text}`;
if (prayer.answeredDate) {
message += ` (Answered: ${new Date(prayer.answeredDate).toLocaleDateString()})`;
}
message += `\n`;
});
}
}
const subject = `Daily Prayer Summary - ${activePrayers.length} active prayers`;
try {
// Send email
await transporter.sendMail({
from: smtpConfig.from,
to: userData.email,
subject,
text: message,
html: generatePrayerEmailHTML(message, subject),
});
// Update last email sent time using Firebase Admin SDK
await admin.firestore().collection('users').doc(userId).update({
lastEmailSent: new Date(),
});
emailsSent++;
console.log(`✅ Sent daily summary email to ${userData.email}`);
} catch (emailError) {
console.error(`❌ Failed to send email to ${userData.email}:`, emailError);
}
}
console.log(`📧 Daily email summary complete. Sent ${emailsSent} emails.`);
return NextResponse.json({
message: `Daily email summary complete. Sent ${emailsSent} emails.`,
emailsSent
});
} catch (error) {
console.error('❌ Scheduled email error:', error);
console.error('❌ Error details:', {
message: error instanceof Error ? error.message : 'Unknown error',
stack: error instanceof Error ? error.stack : undefined,
name: error instanceof Error ? error.name : 'Unknown'
});
return NextResponse.json({
error: 'Failed to process scheduled emails',
details: error instanceof Error ? error.message : 'Unknown error'
}, { status: 500 });
}
}
export async function POST() {
// POST handler calls the same logic as GET for testing purposes
return GET();
}