From 09df7b7830e244eca74b984ec298676f37bb902e Mon Sep 17 00:00:00 2001 From: Pontus Abrahamsson Date: Tue, 26 Nov 2024 12:37:00 +0100 Subject: [PATCH] wip --- .../tasks/bank/notifications/transactions.ts | 52 +++++++++++-------- .../jobs/tasks/bank/scheduler/bank-sync.ts | 1 + .../jobs/tasks/bank/setup/initial.ts | 2 +- .../dashboard/jobs/tasks/bank/sync/account.ts | 1 + .../jobs/tasks/bank/sync/connection.ts | 3 ++ .../jobs/tasks/bank/transactions/upsert.ts | 2 +- .../jobs/utils/transaction-notifications.ts | 23 ++++---- .../providers/gocardless/gocardless-api.ts | 4 +- packages/app-store/package.json | 1 + packages/app-store/src/slack/lib/client.ts | 24 +-------- .../slack/lib/notifications/transactions.ts | 10 ++-- packages/supabase/src/client/job.ts | 7 +++ 12 files changed, 65 insertions(+), 65 deletions(-) diff --git a/apps/dashboard/jobs/tasks/bank/notifications/transactions.ts b/apps/dashboard/jobs/tasks/bank/notifications/transactions.ts index 9705e8e111..6c8c680986 100644 --- a/apps/dashboard/jobs/tasks/bank/notifications/transactions.ts +++ b/apps/dashboard/jobs/tasks/bank/notifications/transactions.ts @@ -1,5 +1,5 @@ import { createClient } from "@midday/supabase/job"; -import { schemaTask } from "@trigger.dev/sdk/v3"; +import { logger, schemaTask } from "@trigger.dev/sdk/v3"; import { handleTransactionEmails, handleTransactionSlackNotifications, @@ -9,36 +9,42 @@ import { z } from "zod"; export const transactionsNotification = schemaTask({ id: "transactions-notification", + maxDuration: 300, schema: z.object({ teamId: z.string(), }), run: async ({ teamId }) => { const supabase = createClient(); - // Mark all transactions as processed and get the ones that need to be notified about - const { data: transactionsData } = await supabase - .from("transactions") - .update({ processed: true }) - .eq("team_id", teamId) - .eq("processed", false) - .select("id, date, amount, name, currency, category, status") - .throwOnError(); + try { + // Mark all transactions as processed and get the ones that need to be notified about + const { data: transactionsData } = await supabase + .from("transactions") + .update({ processed: true }) + .eq("team_id", teamId) + .eq("processed", false) + .select("id, date, amount, name, currency, category, status") + .order("date", { ascending: false }) + .throwOnError(); - const { data: usersData } = await supabase - .from("users_on_team") - .select( - "id, team_id, team:teams(inbox_id, name), user:users(id, full_name, avatar_url, email, locale)", - ) - .eq("team_id", teamId) - .eq("role", "owner") - .throwOnError(); + const { data: usersData } = await supabase + .from("users_on_team") + .select( + "id, team_id, team:teams(inbox_id, name), user:users(id, full_name, avatar_url, email, locale)", + ) + .eq("team_id", teamId) + .eq("role", "owner") + .throwOnError(); - const sortedTransactions = transactionsData?.sort( - (a, b) => new Date(b.date).getTime() - new Date(a.date).getTime(), - ); + if (transactionsData && transactionsData.length > 0) { + await handleTransactionNotifications(usersData, transactionsData); + await handleTransactionEmails(usersData, transactionsData); + await handleTransactionSlackNotifications(teamId, transactionsData); + } + } catch (error) { + await logger.error("Transactions notification", { error }); - await handleTransactionNotifications(usersData, sortedTransactions); - await handleTransactionEmails(usersData, sortedTransactions); - await handleTransactionSlackNotifications(teamId, sortedTransactions); + throw error; + } }, }); diff --git a/apps/dashboard/jobs/tasks/bank/scheduler/bank-sync.ts b/apps/dashboard/jobs/tasks/bank/scheduler/bank-sync.ts index 766598b979..168b60331f 100644 --- a/apps/dashboard/jobs/tasks/bank/scheduler/bank-sync.ts +++ b/apps/dashboard/jobs/tasks/bank/scheduler/bank-sync.ts @@ -6,6 +6,7 @@ import { syncConnection } from "../sync/connection"; // that has a status of "connected". export const bankSyncScheduler = schedules.task({ id: "bank-sync-scheduler", + maxDuration: 600, run: async (payload) => { const supabase = createClient(); diff --git a/apps/dashboard/jobs/tasks/bank/setup/initial.ts b/apps/dashboard/jobs/tasks/bank/setup/initial.ts index 4eb1036ac9..1eb33d5d85 100644 --- a/apps/dashboard/jobs/tasks/bank/setup/initial.ts +++ b/apps/dashboard/jobs/tasks/bank/setup/initial.ts @@ -12,7 +12,7 @@ export const initialBankSetup = schemaTask({ teamId: z.string().uuid(), connectionId: z.string().uuid(), }), - maxDuration: 600, + maxDuration: 300, queue: { concurrencyLimit: 20, }, diff --git a/apps/dashboard/jobs/tasks/bank/sync/account.ts b/apps/dashboard/jobs/tasks/bank/sync/account.ts index 9e94ae46c4..b40c870916 100644 --- a/apps/dashboard/jobs/tasks/bank/sync/account.ts +++ b/apps/dashboard/jobs/tasks/bank/sync/account.ts @@ -10,6 +10,7 @@ const BATCH_SIZE = 500; export const syncAccount = schemaTask({ id: "sync-account", + maxDuration: 300, retry: { maxAttempts: 2, }, diff --git a/apps/dashboard/jobs/tasks/bank/sync/connection.ts b/apps/dashboard/jobs/tasks/bank/sync/connection.ts index b17d2499b6..f2cfa0ff80 100644 --- a/apps/dashboard/jobs/tasks/bank/sync/connection.ts +++ b/apps/dashboard/jobs/tasks/bank/sync/connection.ts @@ -10,6 +10,7 @@ import { syncAccount } from "./account"; // Fan-out pattern. We want to trigger a task for each bank account (Transactions, Balance) export const syncConnection = schemaTask({ id: "sync-connection", + maxDuration: 300, retry: { maxAttempts: 2, }, @@ -41,6 +42,8 @@ export const syncConnection = schemaTask({ }, }); + logger.info("Connection response", { connectionResponse }); + if (!connectionResponse.ok) { logger.error("Failed to get connection status"); throw new Error("Failed to get connection status"); diff --git a/apps/dashboard/jobs/tasks/bank/transactions/upsert.ts b/apps/dashboard/jobs/tasks/bank/transactions/upsert.ts index 73355a143d..c0ca43d271 100644 --- a/apps/dashboard/jobs/tasks/bank/transactions/upsert.ts +++ b/apps/dashboard/jobs/tasks/bank/transactions/upsert.ts @@ -18,7 +18,7 @@ const transactionSchema = z.object({ export const upsertTransactions = schemaTask({ id: "upsert-transactions", - maxDuration: 20000, + maxDuration: 300, queue: { concurrencyLimit: 10, }, diff --git a/apps/dashboard/jobs/utils/transaction-notifications.ts b/apps/dashboard/jobs/utils/transaction-notifications.ts index aceaa6d8ef..3f3b725851 100644 --- a/apps/dashboard/jobs/utils/transaction-notifications.ts +++ b/apps/dashboard/jobs/utils/transaction-notifications.ts @@ -1,4 +1,4 @@ -import { sendSlackTransactionsNotification } from "@midday/app-store/slack"; +import { sendSlackTransactionsNotification } from "@midday/app-store/slack-notifications"; import TransactionsEmail from "@midday/email/emails/transactions"; import { getI18n } from "@midday/email/locales"; import { getInboxEmail } from "@midday/inbox"; @@ -7,6 +7,7 @@ import { TriggerEvents, triggerBulk, } from "@midday/notification"; +import { createClient } from "@midday/supabase/job"; import { render } from "@react-email/components"; import { logger } from "@trigger.dev/sdk/v3"; @@ -41,7 +42,7 @@ interface Transaction { export async function handleTransactionNotifications( usersData: UserData[], - sortedTransactions: Transaction[], + transactions: Transaction[], ) { const notificationEvents = usersData.map(({ user, team_id }) => { const { t } = getI18n({ locale: user.locale ?? "en" }); @@ -50,10 +51,10 @@ export async function handleTransactionNotifications( name: TriggerEvents.TransactionsNewInApp, payload: { type: NotificationTypes.Transactions, - from: sortedTransactions[sortedTransactions.length - 1]?.date, - to: sortedTransactions[0]?.date, + from: transactions[transactions.length - 1]?.date, + to: transactions[0]?.date, description: t("notifications.transactions", { - numberOfTransactions: sortedTransactions.length, + numberOfTransactions: transactions.length, }), }, user: { @@ -79,7 +80,7 @@ export async function handleTransactionNotifications( export async function handleTransactionEmails( usersData: UserData[], - sortedTransactions: Transaction[], + transactions: Transaction[], ) { const emailPromises = usersData.map(async ({ user, team_id, team }) => { const { t } = getI18n({ locale: user.locale ?? "en" }); @@ -87,7 +88,7 @@ export async function handleTransactionEmails( const html = await render( TransactionsEmail({ fullName: user.full_name, - transactions: sortedTransactions, + transactions, locale: user.locale ?? "en", teamName: team.name, }), @@ -123,11 +124,12 @@ export async function handleTransactionEmails( export async function handleTransactionSlackNotifications( teamId: string, - sortedTransactions: Transaction[], + transactions: Transaction[], ) { + const supabase = createClient(); + // TODO: Get correct locale for formatting the amount - const slackTransactions = sortedTransactions.map((transaction) => ({ - date: transaction.date, + const slackTransactions = transactions.map((transaction) => ({ amount: Intl.NumberFormat("en-US", { style: "currency", currency: transaction.currency, @@ -138,5 +140,6 @@ export async function handleTransactionSlackNotifications( await sendSlackTransactionsNotification({ teamId, transactions: slackTransactions, + supabase, }); } diff --git a/apps/engine/src/providers/gocardless/gocardless-api.ts b/apps/engine/src/providers/gocardless/gocardless-api.ts index 9d9fe2cf27..253f4bc6cc 100644 --- a/apps/engine/src/providers/gocardless/gocardless-api.ts +++ b/apps/engine/src/providers/gocardless/gocardless-api.ts @@ -30,8 +30,8 @@ export class GoCardLessApi { #baseUrl = "https://bankaccountdata.gocardless.com"; // Cache keys - #accessTokenCacheKey = "gocardless_access_token"; - #refreshTokenCacheKey = "gocardless_refresh_token"; + #accessTokenCacheKey = "gocardless_access_token_v2"; + #refreshTokenCacheKey = "gocardless_refresh_token_v2"; #institutionsCacheKey = "gocardless_institutions"; #kv: KVNamespace; diff --git a/packages/app-store/package.json b/packages/app-store/package.json index 2e9b0fed8e..34cb0f3105 100644 --- a/packages/app-store/package.json +++ b/packages/app-store/package.json @@ -23,6 +23,7 @@ "exports": { ".": "./src/index.ts", "./slack": "./src/slack/index.ts", + "./slack-notifications": "./src/slack/lib/notifications/transactions.ts", "./db": "./src/db/index.ts" } } diff --git a/packages/app-store/src/slack/lib/client.ts b/packages/app-store/src/slack/lib/client.ts index 8a16577b75..2832d0d093 100644 --- a/packages/app-store/src/slack/lib/client.ts +++ b/packages/app-store/src/slack/lib/client.ts @@ -9,29 +9,9 @@ const SLACK_OAUTH_REDIRECT_URL = const SLACK_STATE_SECRET = process.env.NEXT_PUBLIC_SLACK_STATE_SECRET; const SLACK_SIGNING_SECRET = process.env.SLACK_SIGNING_SECRET; -if (!SLACK_CLIENT_ID) { - throw new Error("SLACK_CLIENT_ID is not defined"); -} - -if (!SLACK_CLIENT_SECRET) { - throw new Error("SLACK_CLIENT_SECRET is not defined"); -} - -if (!SLACK_OAUTH_REDIRECT_URL) { - throw new Error("SLACK_OAUTH_REDIRECT_URL is not defined"); -} - -if (!SLACK_STATE_SECRET) { - throw new Error("SLACK_STATE_SECRET is not defined"); -} - -if (!SLACK_SIGNING_SECRET) { - throw new Error("SLACK_SIGNING_SECRET is not defined"); -} - export const slackInstaller = new InstallProvider({ - clientId: SLACK_CLIENT_ID, - clientSecret: SLACK_CLIENT_SECRET, + clientId: SLACK_CLIENT_ID!, + clientSecret: SLACK_CLIENT_SECRET!, stateSecret: SLACK_STATE_SECRET, logLevel: process.env.NODE_ENV === "development" ? LogLevel.DEBUG : undefined, }); diff --git a/packages/app-store/src/slack/lib/notifications/transactions.ts b/packages/app-store/src/slack/lib/notifications/transactions.ts index 70037107d5..5bf70df7cb 100644 --- a/packages/app-store/src/slack/lib/notifications/transactions.ts +++ b/packages/app-store/src/slack/lib/notifications/transactions.ts @@ -1,10 +1,8 @@ -import { createClient } from "@midday/supabase/server"; +import type { SupabaseClient } from "@supabase/supabase-js"; import { z } from "zod"; -import config from "../../config"; import { createSlackWebClient } from "../client"; const transactionSchema = z.object({ - date: z.coerce.date(), amount: z.string(), name: z.string(), }); @@ -12,17 +10,17 @@ const transactionSchema = z.object({ export async function sendSlackTransactionsNotification({ teamId, transactions, + supabase, }: { teamId: string; transactions: z.infer[]; + supabase: SupabaseClient; }) { - const supabase = createClient({ admin: true }); - const { data } = await supabase .from("apps") .select("settings, config") .eq("team_id", teamId) - .eq("app_id", config.id) + .eq("app_id", "slack") .single(); const enabled = data?.settings?.find( diff --git a/packages/supabase/src/client/job.ts b/packages/supabase/src/client/job.ts index 5383ece0b3..7d44be2300 100644 --- a/packages/supabase/src/client/job.ts +++ b/packages/supabase/src/client/job.ts @@ -5,4 +5,11 @@ export const createClient = () => createSupabaseClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.SUPABASE_SERVICE_KEY!, + { + global: { + headers: { + "sb-lb-routing-mode": "alpha-all-services", + }, + }, + }, );