Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP 🚧 chore(T-244): Migrate to trigger dev v3 #170

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/feature-flags/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"@basestack/ui": "*",
"@basestack/utils": "*",
"@emotion/is-prop-valid": "^1.3.1",
"@floating-ui/react": "^0.26.24",
"@floating-ui/react": "^0.26.25",
"@hookform/resolvers": "^3.9.0",
"@monaco-editor/react": "^4.6.0",
"@next-auth/prisma-adapter": "^1.0.4",
Expand Down
3 changes: 1 addition & 2 deletions apps/forms/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,8 @@ AUTH0_DOMAIN=

# V2 - Trigger
TRIGGER_PROJECT_ID=
TRIGGER_API_KEY=
TRIGGER_SECRET_KEY=
TRIGGER_API_URL=
NEXT_PUBLIC_TRIGGER_PUBLIC_API_KEY=

# V3 - Trigger
TRIGGER_PROJECT_ID=
Expand Down
13 changes: 0 additions & 13 deletions apps/forms/libs/trigger/index.ts

This file was deleted.

90 changes: 42 additions & 48 deletions apps/forms/libs/trigger/jobs/ai/spam.tsx
Original file line number Diff line number Diff line change
@@ -1,62 +1,56 @@
import { eventTrigger } from "@trigger.dev/sdk";
import { triggerClient, TriggerEventName } from "libs/trigger";
import { logger, task } from "@trigger.dev/sdk/v3";
// Prisma
import prisma from "libs/prisma";
// AI
import { TextGenerationModel, cfAiClient, instructions } from "libs/cf/ai";
// Utils
import { z } from "zod";
import { cfAiClient, instructions, TextGenerationModel } from "libs/cf/ai";

triggerClient.defineJob({
export interface CheckForSpamPayload {
submissionId: string;
data: any;
}

export const checkDataForSpamTask = task({
id: "check-data-for-spam",
name: "Check data for spam",
version: "1.0.0",
trigger: eventTrigger({
name: TriggerEventName.CHECK_DATA_FOR_SPAM,
schema: z.object({
submissionId: z.string(),
data: z.any(),
}),
}),
run: async (payload, io, ctx) => {
await io.logger.info(
machine: {
preset: "small-1x",
},
init: async (payload) => {
logger.info(
`Preparing to check data for spam: ${JSON.stringify(payload.data)} with submission ID: ${payload.submissionId}`,
);
},
run: async (payload: CheckForSpamPayload) => {
const res = await cfAiClient({
model: TextGenerationModel.LLAMA_3_8B_INSTRUCT,
messages: instructions.checkSpam(JSON.stringify(payload.data)),
});

await io.runTask(
"check-data-for-spam",
async () => {
const res = await cfAiClient({
model: TextGenerationModel.LLAMA_3_8B_INSTRUCT,
messages: instructions.checkSpam(JSON.stringify(payload.data)),
});

if (res.success) {
await io.logger.info(
`Data successfully checked for: ${res.result.response}`,
);
if (res.success) {
logger.info(`Data successfully checked for: ${res.result.response}`);

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

if (isSpam) {
await io.logger.info(
`The data is spam. Submission ID: ${payload.submissionId}. Preparing to update the form Submission status on the DB.`,
);
if (isSpam) {
logger.info(
`The data is spam. Submission ID: ${payload.submissionId}. Preparing to update the form Submission status on the DB.`,
);

await prisma.submission.update({
where: {
id: payload.submissionId,
},
data: {
isSpam: true,
},
});
}
}
},
{ name: "Check data for Spam" },
);
return prisma.submission.update({
where: {
id: payload.submissionId,
},
data: {
isSpam: true,
},
});
}

await io.logger.info("✨ Data successfully checked ✨");
return {
isSpam,
};
}
},
onSuccess: async () => {
logger.info("✨ Data successfully checked ✨");
},
});
12 changes: 6 additions & 6 deletions apps/forms/libs/trigger/jobs/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// Notification Jobs
// AI
export * from "./ai/spam";
// Notification
export * from "./notification/email";
// Webhook Jobs
// Webhook
export * from "./webhook/external";
// AI Jobs
export * from "./ai/spam";
// Subscription Jobs
export * from "./subscription/update";
// Subscriptions
export * from "./subscription/check";
export * from "./subscription/update";
79 changes: 35 additions & 44 deletions apps/forms/libs/trigger/jobs/notification/email.tsx
Original file line number Diff line number Diff line change
@@ -1,62 +1,53 @@
import React from "react";
import { eventTrigger } from "@trigger.dev/sdk";
import { triggerClient, TriggerEventName } from "libs/trigger";
import { task, logger } from "@trigger.dev/sdk/v3";
// Email
import { sendEmail, NewSubmissionEmailTemplate } from "@basestack/emails";
import { render } from "@react-email/render";
// Utils
import { z } from "zod";

export interface SendEmailPayload {
to: string[];
subject: string;
template: string;
props?: any;
}

const template: { [key: string]: React.ElementType } = {
"new-submission": NewSubmissionEmailTemplate,
};

triggerClient.defineJob({
export const sendEmailTask = task({
id: "send-email",
name: "Send email",
version: "1.0.0",
trigger: eventTrigger({
name: TriggerEventName.SEND_EMAIL,
schema: z.object({
to: z.array(z.string().email()),
subject: z.string(),
template: z.string(),
props: z.any().optional(),
}),
}),
run: async (payload, io) => {
await io.logger.info(
machine: {
preset: "small-1x",
},
init: async (payload) => {
logger.info(
`Preparing to send email to ${payload.to} with subject: ${payload.subject}`,
);

await io.logger.info(
logger.info(
`Email with the template ${payload.template} with props: ${payload.props}`,
);

await io.runTask(
"send-email",
async () => {
const Template = template[payload.template];

await Promise.all(
payload.to.map(async (email) => {
const html = await render(<Template {...payload.props} />);

await sendEmail({
html,
options: {
subject: payload.subject,
from: process.env.EMAIL_FROM!,
to: email,
},
});
}),
);
},

{ name: "Send Email" },
},
run: async (payload: SendEmailPayload) => {
const Template = template[payload.template];

await Promise.all(
payload.to.map(async (email) => {
const html = await render(<Template {...payload.props} />);

await sendEmail({
html,
options: {
subject: payload.subject,
from: process.env.EMAIL_FROM!,
to: email,
},
});
}),
);

await io.logger.info("✨ Email sent successfully! ✨");
},
onSuccess: async () => {
logger.info("✨ Email sent successfully! ✨");
},
});
117 changes: 56 additions & 61 deletions apps/forms/libs/trigger/jobs/subscription/check.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { cronTrigger } from "@trigger.dev/sdk";
import { triggerClient } from "libs/trigger";
import { schedules, logger } from "@trigger.dev/sdk/v3";
// Prisma
import prisma from "libs/prisma";
// Utils
Expand All @@ -8,76 +7,72 @@ import { PlanTypeId, config, SubscriptionEvent } from "@basestack/utils";

const { getFormPlanLimitsDefaults } = config.plans;

triggerClient.defineJob({
export const checkSubsStatusTask = schedules.task({
id: "check.subscription.status.cron",
name: "Check Subscription Status Cron Job",
version: "0.1.1",
trigger: cronTrigger({
cron: "0 19 * * *", // Run every day at 7 PM
}),
run: async (payload, io, ctx) => {
await io.logger.info("Received the scheduled event", {
cron: "0 19 * * *", // Run every day at 7 PM
machine: {
preset: "small-2x",
},
init: async (payload) => {
logger.info("Received the scheduled event", {
payload,
});
},
run: async () => {
const subs = await prisma.subscription.findMany();

await io.runTask(
"check-users-subscriptions",
async () => {
const subs = await prisma.subscription.findMany();

for (const sub of subs) {
const billingCycle = dayjs(sub.billingCycleStart);
const today = dayjs();
const cycleStartDate = dayjs(sub.billingCycleStart);

// Check if today is the billing cycle start date or later
const isOverdue =
today.isAfter(cycleStartDate.startOf("day")) ||
today.isSame(cycleStartDate.startOf("day"));
for (const sub of subs) {
const billingCycle = dayjs(sub.billingCycleStart);
const today = dayjs();
const cycleStartDate = dayjs(sub.billingCycleStart);

await io.logger.info(
`User with ID: ${sub.userId} subscription billingCycle is ${billingCycle.format("YYYY-MM-DD")} and is overdue: ${isOverdue}`,
);
// Check if today is the billing cycle start date or later
const isOverdue =
today.isAfter(cycleStartDate.startOf("day")) ||
today.isSame(cycleStartDate.startOf("day"));

// Check if it's time to update the subscription
if (isOverdue) {
let payload = {};
logger.info(
`User with ID: ${sub.userId} subscription billingCycle is ${billingCycle.format("YYYY-MM-DD")} and is overdue: ${isOverdue}`,
);

if (sub.cancelled || sub.paused) {
await io.logger.info(
`User with ID ${sub.userId} has an cancelled or paused subscription`,
);
// Check if it's time to update the subscription
if (isOverdue) {
let payload = {};

payload = {
planId: PlanTypeId.FREE,
event: SubscriptionEvent.SUBSCRIPTION_CANCELLED,
};
}

const response = await prisma.subscription.update({
where: {
id: sub.id,
},
data: {
billingCycleStart: dayjs(cycleStartDate)
.add(1, "month")
.toISOString(),
...getFormPlanLimitsDefaults(),
...payload,
},
});
if (sub.cancelled || sub.paused) {
logger.info(
`User with ID ${sub.userId} has an cancelled or paused subscription`,
);

await io.logger.info(
`User ${sub.userId} subscription updated successfully`,
response,
);
}
payload = {
planId: PlanTypeId.FREE,
event: SubscriptionEvent.SUBSCRIPTION_CANCELLED,
};
}
},

{ name: "Checking users subscriptions" },
);
const response = await prisma.subscription.update({
where: {
id: sub.id,
},
data: {
billingCycleStart: dayjs(cycleStartDate)
.add(1, "month")
.toISOString(),
...getFormPlanLimitsDefaults(),
...payload,
},
});

await io.logger.info("✨ Subscription status checked successfully! ✨");
logger.info(
`User ${sub.userId} subscription updated successfully`,
response,
);

return response;
}
}
},
onSuccess: async () => {
logger.info("✨ Subscription status checked successfully! ✨");
},
});
Loading