Skip to content

Commit

Permalink
chore(T-220): Email templates & UpStash Workflow Update (#178)
Browse files Browse the repository at this point in the history
* create email templates

* fix title

* chore: update the send email with the new template

---------

Co-authored-by: amaralflavio <[email protected]>
  • Loading branch information
vacom and amaralflavio authored Dec 10, 2024
1 parent 13dbe18 commit 6005cf4
Show file tree
Hide file tree
Showing 14 changed files with 598 additions and 131 deletions.
23 changes: 16 additions & 7 deletions apps/forms/app/api/v1/jobs/check-spam/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// UpStash
import { serve } from "@upstash/qstash/nextjs";
import { serve } from "@upstash/workflow/nextjs";
import { Receiver } from "@upstash/qstash";
// Types
import type { CheckDataForSpamPayload } from "libs/qstash";
Expand All @@ -8,12 +8,12 @@ import { TextGenerationModel, cfAiClient, instructions } from "libs/cf/ai";
// Prisma
import prisma from "libs/prisma";

export const POST = serve<CheckDataForSpamPayload>(
export const { POST } = serve<CheckDataForSpamPayload>(
async (context) => {
const { submissionId, data } = context.requestPayload;

console.info(
`Preparing to check data for spam: ${JSON.stringify(data)} with submission ID: ${submissionId}`,
`Job: Check Spam - Preparing to check data for spam: ${JSON.stringify(data)} with submission ID: ${submissionId}`,
);

await context.run("check-data-for-spam-step", async () => {
Expand All @@ -23,13 +23,15 @@ export const POST = serve<CheckDataForSpamPayload>(
});

if (res.success) {
console.info(`Data successfully checked for: ${res.result.response}`);
console.info(
`Job: Check Spam - Data successfully checked for: ${res.result.response}`,
);

const isSpam = JSON.parse(res.result.response).isSpam ?? false;

if (isSpam) {
console.info(
`The data is spam. Submission ID: ${submissionId}. Preparing to update the form Submission status on the DB.`,
`Job: Check Spam - The data is spam. Submission ID: ${submissionId}. Preparing to update the form Submission status on the DB.`,
);

await prisma.submission.update({
Expand All @@ -49,8 +51,15 @@ export const POST = serve<CheckDataForSpamPayload>(
currentSigningKey: process.env.QSTASH_CURRENT_SIGNING_KEY!,
nextSigningKey: process.env.QSTASH_NEXT_SIGNING_KEY!,
}),
failureFunction: async (context, failStatus, failResponse, failHeaders) => {
// Handle error, i.e. log to a log provider
failureFunction: async ({
context,
failStatus,
failResponse,
failHeaders,
}) => {
console.error(
`Job: Check Spam - status = ${JSON.stringify(failStatus)} response = ${JSON.stringify(failResponse)} headers = ${JSON.stringify(failHeaders)} context = ${JSON.stringify(context)} `,
);
},
},
);
22 changes: 16 additions & 6 deletions apps/forms/app/api/v1/jobs/check-subscription/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// UpStash
import { serve } from "@upstash/qstash/nextjs";
import { serve } from "@upstash/workflow/nextjs";
import { Receiver } from "@upstash/qstash";
// Prisma
import prisma from "libs/prisma";
Expand All @@ -11,9 +11,9 @@ const { getFormPlanLimitsDefaults } = config.plans;

// cron: "0 19 * * *", // Run every day at 7 PM

export const POST = serve(
export const { POST } = serve(
async (context) => {
console.info("Received the scheduled event");
console.info("Job: Check Subscriptions - Received the scheduled event");

await context.run("check-users-subscriptions-step", async () => {
const subs = await prisma.subscription.findMany();
Expand All @@ -28,7 +28,7 @@ export const POST = serve(
today.isSame(billingCycle.startOf("day"));

console.info(
`User with ID: ${sub.userId} subscription billingCycle is ${billingCycle.format("YYYY-MM-DD")} and is overdue: ${isOverdue}`,
`Job: Check Subscriptions - User with ID: ${sub.userId} subscription billingCycle is ${billingCycle.format("YYYY-MM-DD")} and is overdue: ${isOverdue}`,
);

// Check if it's time to update the subscription
Expand All @@ -37,7 +37,7 @@ export const POST = serve(

if (sub.cancelled || sub.paused) {
console.info(
`User with ID ${sub.userId} has an cancelled or paused subscription`,
`Job: Check Subscriptions - User with ID ${sub.userId} has an cancelled or paused subscription`,
);

payload = {
Expand All @@ -61,7 +61,7 @@ export const POST = serve(
});

console.info(
`User ${sub.userId} subscription updated successfully`,
`Job: Check Subscriptions - User ${sub.userId} subscription updated successfully`,
response,
);
}
Expand All @@ -73,5 +73,15 @@ export const POST = serve(
currentSigningKey: process.env.QSTASH_CURRENT_SIGNING_KEY!,
nextSigningKey: process.env.QSTASH_NEXT_SIGNING_KEY!,
}),
failureFunction: async ({
context,
failStatus,
failResponse,
failHeaders,
}) => {
console.error(
`Job: Check Subscriptions - status = ${JSON.stringify(failStatus)} response = ${JSON.stringify(failResponse)} headers = ${JSON.stringify(failHeaders)} context = ${JSON.stringify(context)} `,
);
},
},
);
24 changes: 19 additions & 5 deletions apps/forms/app/api/v1/jobs/send-data-to-external-webhook/route.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
// UpStash
import { serve } from "@upstash/qstash/nextjs";
import { serve } from "@upstash/workflow/nextjs";
import { Receiver } from "@upstash/qstash";
// Types
import type { SendDataToExternalWebhookPayload } from "libs/qstash";

export const POST = serve<SendDataToExternalWebhookPayload>(
export const { POST } = serve<SendDataToExternalWebhookPayload>(
async (context) => {
const { url, body } = context.requestPayload;

console.info(`Preparing to send data to external webhook: ${url}`);
console.info(
`Job: External Webhook - Preparing to send data to external webhook: ${url}`,
);

await context.run("send-data-to-external-webhook-step", async () => {
const res = await fetch(url, {
Expand All @@ -20,16 +22,28 @@ export const POST = serve<SendDataToExternalWebhookPayload>(
});

console.info(
`Data sent to the external webhook with status: ${res.status}`,
`Job: External Webhook - Data sent to the external webhook with status: ${res.status}`,
);
});

console.info("✨ Data successfully sent to the external webhook ✨");
console.info(
"Job: External Webhook - ✨ Data successfully sent to the external webhook ✨",
);
},
{
receiver: new Receiver({
currentSigningKey: process.env.QSTASH_CURRENT_SIGNING_KEY!,
nextSigningKey: process.env.QSTASH_NEXT_SIGNING_KEY!,
}),
failureFunction: async ({
context,
failStatus,
failResponse,
failHeaders,
}) => {
console.error(
`Job: External Webhook - status = ${JSON.stringify(failStatus)} response = ${JSON.stringify(failResponse)} headers = ${JSON.stringify(failHeaders)} context = ${JSON.stringify(context)} `,
);
},
},
);
31 changes: 25 additions & 6 deletions apps/forms/app/api/v1/jobs/send-email/route.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,32 @@
// UpStash
import { serve } from "@upstash/qstash/nextjs";
import { serve } from "@upstash/workflow/nextjs";
import { Receiver } from "@upstash/qstash";
// Types
import React, { ElementType } from "react";
import type { SendEmailPayload } from "libs/qstash";
// Email
import { sendEmail, NewSubmissionEmailTemplate } from "@basestack/emails";
import {
sendEmail,
NewSubmissionEmailTemplate,
WelcomeEmailTemplate,
} from "@basestack/emails";
import { render } from "@react-email/render";

const templateList: { [key: string]: ElementType } = {
"new-submission": NewSubmissionEmailTemplate,
welcome: WelcomeEmailTemplate,
};

export const POST = serve<SendEmailPayload>(
export const { POST } = serve<SendEmailPayload>(
async (context) => {
const { to, subject, template, props } = context.requestPayload;

console.info(`Preparing to send email to ${to} with subject: ${subject}`);
console.info(`Email with the template ${template} with props: ${props}`);
console.info(
`Job: Send Email - Preparing to send email to ${to} with subject: ${subject}`,
);
console.info(
`Job: Send Email - Email with the template ${template} with props: ${JSON.stringify(props)}`,
);

await context.run("send-email-step", async () => {
const EmailTemplate = templateList[template];
Expand All @@ -37,13 +46,23 @@ export const POST = serve<SendEmailPayload>(
}),
);

console.info("✨ Email sent successfully! ✨");
console.info("Job: Send Email - ✨ Email sent successfully! ✨");
});
},
{
receiver: new Receiver({
currentSigningKey: process.env.QSTASH_CURRENT_SIGNING_KEY!,
nextSigningKey: process.env.QSTASH_NEXT_SIGNING_KEY!,
}),
failureFunction: async ({
context,
failStatus,
failResponse,
failHeaders,
}) => {
console.error(
`Job: Send Email - status = ${JSON.stringify(failStatus)} response = ${JSON.stringify(failResponse)} headers = ${JSON.stringify(failHeaders)} context = ${JSON.stringify(context)} `,
);
},
},
);
28 changes: 22 additions & 6 deletions apps/forms/app/api/v1/jobs/update-subscription/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// UpStash
import { serve } from "@upstash/qstash/nextjs";
import { serve } from "@upstash/workflow/nextjs";
import { Receiver } from "@upstash/qstash";
// Types
import type { UpdateSubscriptionEventPayload } from "libs/qstash";
Expand All @@ -11,15 +11,15 @@ import { PlanTypeId, config, SubscriptionEvent } from "@basestack/utils";

const { getSubscriptionEvents, getFormPlanByVariantId } = config.plans;

export const POST = serve<UpdateSubscriptionEventPayload>(
export const { POST } = serve<UpdateSubscriptionEventPayload>(
async (context) => {
const body = context.requestPayload;

console.info(`Webhook Event Body`, body);
console.info(`Job: Update Subscriptions - Webhook Event Body`, body);
console.info(`Subscription Event:${body.meta.event_name}`);

if (!getSubscriptionEvents.includes(body.meta.event_name)) {
console.error("Invalid event name received");
console.error("Job: Update Subscriptions - Invalid event name received");
return;
}

Expand All @@ -35,7 +35,10 @@ export const POST = serve<UpdateSubscriptionEventPayload>(
async () => {
const userId = body.meta.custom_data.user_id;

console.info("Checking if user has a subscription", userId);
console.info(
"Job: Update Subscriptions - Checking if user has a subscription",
userId,
);

return prisma.subscription.findFirst({
where: {
Expand Down Expand Up @@ -91,7 +94,10 @@ export const POST = serve<UpdateSubscriptionEventPayload>(
},
});

console.info("Subscription updated on DB", res);
console.info(
"Job: Update Subscriptions - Subscription updated on DB",
res,
);
});
}
}
Expand All @@ -101,5 +107,15 @@ export const POST = serve<UpdateSubscriptionEventPayload>(
currentSigningKey: process.env.QSTASH_CURRENT_SIGNING_KEY!,
nextSigningKey: process.env.QSTASH_NEXT_SIGNING_KEY!,
}),
failureFunction: async ({
context,
failStatus,
failResponse,
failHeaders,
}) => {
console.error(
`Job: Update Subscriptions - status = ${JSON.stringify(failStatus)} response = ${JSON.stringify(failResponse)} headers = ${JSON.stringify(failHeaders)} context = ${JSON.stringify(context)} `,
);
},
},
);
13 changes: 12 additions & 1 deletion apps/forms/libs/auth/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { PrismaAdapter } from "@next-auth/prisma-adapter";
import prisma from "libs/prisma";
// Types
import { Role } from "@prisma/client";
// Jobs
import { sendEmailEvent } from "libs/qstash";

declare module "next-auth" {
interface Session {
Expand Down Expand Up @@ -122,7 +124,16 @@ export const authOptions: NextAuthOptions = {
new Date(user.createdAt).getTime() > Date.now() - 10000;

if (isNewUserRecently) {
// TODO: Trigger a background job to send a welcome email
await sendEmailEvent({
template: "welcome",
to: [email],
subject: `Welcome to Basestack Forms`,
props: {
content: {
name: user.name,
},
},
});
}
}
},
Expand Down
1 change: 1 addition & 0 deletions apps/forms/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"@trpc/react-query": "^10.45.2",
"@trpc/server": "^10.45.2",
"@upstash/qstash": "^2.7.17",
"@upstash/workflow": "^0.2.1",
"cors": "^2.8.5",
"crypto": "^1.0.1",
"dayjs": "^1.11.13",
Expand Down
Loading

0 comments on commit 6005cf4

Please sign in to comment.