143 lines
6.3 KiB
TypeScript
143 lines
6.3 KiB
TypeScript
import {inngest} from "@/lib/inngest/client";
|
|
import {NEWS_SUMMARY_EMAIL_PROMPT, PERSONALIZED_WELCOME_EMAIL_PROMPT} from "@/lib/inngest/prompts";
|
|
import {sendNewsSummaryEmail, sendWelcomeEmail} from "@/lib/nodemailer";
|
|
import {getAllUsersForNewsEmail} from "@/lib/actions/user.actions";
|
|
import { getWatchlistSymbolsByEmail } from "@/lib/actions/watchlist.actions";
|
|
import { getNews } from "@/lib/actions/finnhub.actions";
|
|
import { getFormattedTodayDate } from "@/lib/utils";
|
|
|
|
export const sendSignUpEmail = inngest.createFunction(
|
|
{ id: 'sign-up-email' },
|
|
{ event: 'app/user.created'},
|
|
async ({ event, step }) => {
|
|
const userProfile = `
|
|
- Country: ${event.data.country}
|
|
- Investment goals: ${event.data.investmentGoals}
|
|
- Risk tolerance: ${event.data.riskTolerance}
|
|
- Preferred industry: ${event.data.preferredIndustry}
|
|
`
|
|
|
|
const prompt = PERSONALIZED_WELCOME_EMAIL_PROMPT.replace('{{userProfile}}', userProfile)
|
|
|
|
const response = await step.ai.infer('generate-welcome-intro', {
|
|
model: step.ai.models.gemini({ model: 'gemini-2.5-flash-lite' }),
|
|
body: {
|
|
contents: [
|
|
{
|
|
role: 'user',
|
|
parts: [
|
|
{ text: prompt }
|
|
]
|
|
}]
|
|
}
|
|
})
|
|
|
|
await step.run('send-welcome-email', async () => {
|
|
try {
|
|
const part = response.candidates?.[0]?.content?.parts?.[0];
|
|
const introText = (part && 'text' in part ? part.text : null) ||'Thanks for joining Openstock. You now have the tools to track markets and make smarter moves.'
|
|
|
|
const { data: { email, name } } = event;
|
|
|
|
console.log(`📧 Attempting to send welcome email to: ${email}`);
|
|
const result = await sendWelcomeEmail({ email, name, intro: introText });
|
|
console.log(`✅ Welcome email sent successfully to: ${email}`);
|
|
return result;
|
|
} catch (error) {
|
|
console.error('❌ Error sending welcome email:', error);
|
|
throw error;
|
|
}
|
|
})
|
|
|
|
return {
|
|
success: true,
|
|
message: 'Welcome email sent successfully'
|
|
}
|
|
}
|
|
)
|
|
|
|
export const sendDailyNewsSummary = inngest.createFunction(
|
|
{ id: 'daily-news-summary' },
|
|
[ { event: 'app/send.daily.news' }, { cron: '0 12 * * *' } ],
|
|
async ({ step }) => {
|
|
// Step #1: Get all users for news delivery
|
|
const users = await step.run('get-all-users', getAllUsersForNewsEmail)
|
|
|
|
if(!users || users.length === 0) return { success: false, message: 'No users found for news email' };
|
|
|
|
// Step #2: For each user, get watchlist symbols -> fetch news (fallback to general)
|
|
const results = await step.run('fetch-user-news', async () => {
|
|
const perUser: Array<{ user: User; articles: MarketNewsArticle[] }> = [];
|
|
for (const user of users as User[]) {
|
|
try {
|
|
const symbols = await getWatchlistSymbolsByEmail(user.email);
|
|
let articles = await getNews(symbols);
|
|
// Enforce max 6 articles per user
|
|
articles = (articles || []).slice(0, 6);
|
|
// If still empty, fallback to general
|
|
if (!articles || articles.length === 0) {
|
|
articles = await getNews();
|
|
articles = (articles || []).slice(0, 6);
|
|
}
|
|
perUser.push({ user, articles });
|
|
} catch (e) {
|
|
console.error('daily-news: error preparing user news', user.email, e);
|
|
perUser.push({ user, articles: [] });
|
|
}
|
|
}
|
|
return perUser;
|
|
});
|
|
|
|
// Step #3: (placeholder) Summarize news via AI
|
|
const userNewsSummaries: { user: User; newsContent: string | null }[] = [];
|
|
|
|
for (const { user, articles } of results) {
|
|
try {
|
|
const prompt = NEWS_SUMMARY_EMAIL_PROMPT.replace('{{newsData}}', JSON.stringify(articles, null, 2));
|
|
|
|
const response = await step.ai.infer(`summarize-news-${user.email}`, {
|
|
model: step.ai.models.gemini({ model: 'gemini-2.5-flash-lite' }),
|
|
body: {
|
|
contents: [{ role: 'user', parts: [{ text:prompt }]}]
|
|
}
|
|
});
|
|
|
|
const part = response.candidates?.[0]?.content?.parts?.[0];
|
|
const newsContent = (part && 'text' in part ? part.text : null) || 'No market news.'
|
|
|
|
userNewsSummaries.push({ user, newsContent });
|
|
} catch (e) {
|
|
console.error('Failed to summarize news for : ', user.email);
|
|
userNewsSummaries.push({ user, newsContent: null });
|
|
}
|
|
}
|
|
|
|
// Step #4: (placeholder) Send the emails
|
|
await step.run('send-news-emails', async () => {
|
|
const results = await Promise.allSettled(
|
|
userNewsSummaries.map(async ({ user, newsContent}) => {
|
|
if(!newsContent) {
|
|
console.log(`⏭️ Skipping email for ${user.email} - no news content`);
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
console.log(`📧 Attempting to send news summary email to: ${user.email}`);
|
|
const result = await sendNewsSummaryEmail({ email: user.email, date: getFormattedTodayDate(), newsContent });
|
|
console.log(`✅ News summary email sent successfully to: ${user.email}`);
|
|
return result;
|
|
} catch (error) {
|
|
console.error(`❌ Failed to send news summary email to ${user.email}:`, error);
|
|
throw error;
|
|
}
|
|
})
|
|
);
|
|
|
|
const successful = results.filter(r => r.status === 'fulfilled').length;
|
|
const failed = results.filter(r => r.status === 'rejected').length;
|
|
console.log(`📊 Email sending summary: ${successful} successful, ${failed} failed`);
|
|
})
|
|
|
|
return { success: true, message: 'Daily news summary emails sent successfully' }
|
|
}
|
|
) |