From 220ce232818544724e442e296235f049c377b718 Mon Sep 17 00:00:00 2001 From: Pontus Abrahamsson Date: Mon, 16 Dec 2024 18:53:23 +0100 Subject: [PATCH] Reconnect accounts --- .../tasks/bank/accounts/update-reference.ts | 42 ------------ .../jobs/tasks/bank/sync/connection.ts | 7 -- .../jobs/tasks/reconnect/connection.ts | 68 +++++++++++++++++++ apps/dashboard/src/actions/schema.ts | 5 +- .../manual-sync-transactions-action.ts | 40 ++--------- .../reconnect-connection-action.ts | 27 ++++++++ .../src/components/bank-connections.tsx | 27 ++++++-- packages/events/src/events.ts | 4 ++ 8 files changed, 133 insertions(+), 87 deletions(-) delete mode 100644 apps/dashboard/jobs/tasks/bank/accounts/update-reference.ts create mode 100644 apps/dashboard/jobs/tasks/reconnect/connection.ts create mode 100644 apps/dashboard/src/actions/transactions/reconnect-connection-action.ts diff --git a/apps/dashboard/jobs/tasks/bank/accounts/update-reference.ts b/apps/dashboard/jobs/tasks/bank/accounts/update-reference.ts deleted file mode 100644 index 2f19bb3105..0000000000 --- a/apps/dashboard/jobs/tasks/bank/accounts/update-reference.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { client } from "@midday/engine/client"; -import { createClient } from "@midday/supabase/job"; -import { schemaTask } from "@trigger.dev/sdk/v3"; -import { z } from "zod"; - -export const updateReference = schemaTask({ - id: "update-reference", - maxDuration: 300, - retry: { - maxAttempts: 1, - factor: 1, - minTimeoutInMs: 60000, // 1 minute - maxTimeoutInMs: 60000, - randomize: false, - }, - schema: z.object({ - referenceId: z.string(), - }), - run: async ({ referenceId }) => { - const supabase = createClient(); - - const accountsResponse = await client.accounts.$get({ - query: { - id: referenceId, - provider: "gocardless", - }, - }); - - const { data: accountsData } = await accountsResponse.json(); - - await Promise.all( - accountsData.map(async (account) => { - return supabase - .from("bank_accounts") - .update({ - account_reference: account.resource_id, - }) - .eq("account_id", account.id); - }), - ); - }, -}); diff --git a/apps/dashboard/jobs/tasks/bank/sync/connection.ts b/apps/dashboard/jobs/tasks/bank/sync/connection.ts index cbc68f98b7..ae27d35b4d 100644 --- a/apps/dashboard/jobs/tasks/bank/sync/connection.ts +++ b/apps/dashboard/jobs/tasks/bank/sync/connection.ts @@ -4,7 +4,6 @@ import { logger, schemaTask } from "@trigger.dev/sdk/v3"; import { revalidateCache } from "jobs/utils/revalidate-cache"; import { triggerSequenceAndWait } from "jobs/utils/trigger-sequence"; import { z } from "zod"; -import { updateReference } from "../accounts/update-reference"; import { transactionNotifications } from "../notifications/transactions"; import { syncAccount } from "./account"; @@ -141,12 +140,6 @@ export const syncConnection = schemaTask({ .update({ status: "disconnected" }) .eq("id", connectionId); } - - if (data.provider === "gocardless") { - await updateReference.trigger({ - referenceId: data.reference_id, - }); - } } catch (error) { logger.error("Failed to check connection status by accounts", { error, diff --git a/apps/dashboard/jobs/tasks/reconnect/connection.ts b/apps/dashboard/jobs/tasks/reconnect/connection.ts new file mode 100644 index 0000000000..06bcae7015 --- /dev/null +++ b/apps/dashboard/jobs/tasks/reconnect/connection.ts @@ -0,0 +1,68 @@ +import { client } from "@midday/engine/client"; +import { createClient } from "@midday/supabase/job"; +import { schemaTask } from "@trigger.dev/sdk/v3"; +import { syncConnection } from "jobs/tasks/bank/sync/connection"; +import { z } from "zod"; + +export const reconnectConnection = schemaTask({ + id: "reconnect-connection", + maxDuration: 1000, + retry: { + maxAttempts: 2, + }, + schema: z.object({ + teamId: z.string().uuid(), + connectionId: z.string().uuid(), + provider: z.string(), + }), + run: async ({ teamId, connectionId, provider }) => { + const supabase = createClient(); + + if (provider === "gocardless") { + // We need to update the reference of the connection + const connection = await client.connections[":reference"].$get({ + param: { reference: teamId }, + }); + + const connectionResponse = await connection.json(); + const referenceId = connectionResponse?.data.id; + + // Update the reference_id of the new connection + if (referenceId) { + await supabase + .from("bank_connections") + .update({ + reference_id: referenceId, + }) + .eq("id", connectionId); + } + + // The account_ids can be different between the old and new connection + // So we need to check for account_reference and update + const accounts = await client.accounts.$get({ + query: { + id: referenceId, + provider: "gocardless", + }, + }); + + const accountsResponse = await accounts.json(); + + await Promise.all( + accountsResponse.data.map(async (account) => { + await supabase + .from("bank_accounts") + .update({ + account_id: account.id, + }) + .eq("account_reference", account.resource_id); + }), + ); + } + + await syncConnection.trigger({ + connectionId, + manualSync: true, + }); + }, +}); diff --git a/apps/dashboard/src/actions/schema.ts b/apps/dashboard/src/actions/schema.ts index e35cc1362b..1b16e8baf3 100644 --- a/apps/dashboard/src/actions/schema.ts +++ b/apps/dashboard/src/actions/schema.ts @@ -429,8 +429,11 @@ export const updateEntriesSchema = z.object({ export const manualSyncTransactionsSchema = z.object({ connectionId: z.string().uuid(), +}); + +export const reconnectConnectionSchema = z.object({ + connectionId: z.string().uuid(), provider: z.string(), - type: z.enum(["reconnect", "sync"]), }); export const createGoCardLessLinkSchema = z.object({ diff --git a/apps/dashboard/src/actions/transactions/manual-sync-transactions-action.ts b/apps/dashboard/src/actions/transactions/manual-sync-transactions-action.ts index ecf9273f28..9e4c1afa3b 100644 --- a/apps/dashboard/src/actions/transactions/manual-sync-transactions-action.ts +++ b/apps/dashboard/src/actions/transactions/manual-sync-transactions-action.ts @@ -2,7 +2,6 @@ import { authActionClient } from "@/actions/safe-action"; import { manualSyncTransactionsSchema } from "@/actions/schema"; -import { client } from "@midday/engine/client"; import { LogEvents } from "@midday/events/events"; import { syncConnection } from "jobs/tasks/bank/sync/connection"; @@ -15,36 +14,11 @@ export const manualSyncTransactionsAction = authActionClient channel: LogEvents.TransactionsManualSync.channel, }, }) - .action( - async ({ - parsedInput: { connectionId, provider, type }, - ctx: { user, supabase }, - }) => { - if (provider === "gocardless" && type === "reconnect") { - // We need to update the reference of the connection - const connection = await client.connections[":reference"].$get({ - param: { reference: user.team_id! }, - }); + .action(async ({ parsedInput: { connectionId } }) => { + const event = await syncConnection.trigger({ + connectionId, + manualSync: true, + }); - const connectionResponse = await connection.json(); - - // Update the reference_id of the new connection - // In GoCardLess terms this is the requisition id - if (connectionResponse.data) { - await supabase - .from("bank_connections") - .update({ - reference_id: connectionResponse.data.id, - }) - .eq("id", connectionId); - } - } - - const event = await syncConnection.trigger({ - connectionId, - manualSync: true, - }); - - return event; - }, - ); + return event; + }); diff --git a/apps/dashboard/src/actions/transactions/reconnect-connection-action.ts b/apps/dashboard/src/actions/transactions/reconnect-connection-action.ts new file mode 100644 index 0000000000..8dfd144cad --- /dev/null +++ b/apps/dashboard/src/actions/transactions/reconnect-connection-action.ts @@ -0,0 +1,27 @@ +"use server"; + +import { authActionClient } from "@/actions/safe-action"; +import { reconnectConnectionSchema } from "@/actions/schema"; +import { LogEvents } from "@midday/events/events"; +import { reconnectConnection } from "jobs/tasks/reconnect/connection"; + +export const reconnectConnectionAction = authActionClient + .schema(reconnectConnectionSchema) + .metadata({ + name: "reconnect-connection", + track: { + event: LogEvents.ReconnectConnection.name, + channel: LogEvents.ReconnectConnection.channel, + }, + }) + .action( + async ({ parsedInput: { connectionId, provider }, ctx: { user } }) => { + const event = await reconnectConnection.trigger({ + teamId: user.team_id!, + connectionId, + provider, + }); + + return event; + }, + ); diff --git a/apps/dashboard/src/components/bank-connections.tsx b/apps/dashboard/src/components/bank-connections.tsx index c1840ca10e..4d84291389 100644 --- a/apps/dashboard/src/components/bank-connections.tsx +++ b/apps/dashboard/src/components/bank-connections.tsx @@ -1,6 +1,7 @@ "use client"; import { manualSyncTransactionsAction } from "@/actions/transactions/manual-sync-transactions-action"; +import { reconnectConnectionAction } from "@/actions/transactions/reconnect-connection-action"; import { useSyncStatus } from "@/hooks/use-sync-status"; import { connectionStatus } from "@/utils/connection-status"; import { @@ -167,6 +168,27 @@ export function BankConnection({ connection }: BankConnectionProps) { }, }); + const reconnectConnection = useAction(reconnectConnectionAction, { + onExecute: () => setSyncing(true), + onSuccess: ({ data }) => { + if (data) { + setRunId(data.id); + setAccessToken(data.publicAccessToken); + } + }, + onError: () => { + setSyncing(false); + setRunId(undefined); + setStatus("FAILED"); + + toast({ + duration: 3500, + variant: "error", + title: "Something went wrong please try again.", + }); + }, + }); + useEffect(() => { if (isSyncing) { toast({ @@ -204,10 +226,9 @@ export function BankConnection({ connection }: BankConnectionProps) { // NOTE: GoCardLess reconnect flow (redirect from API route) useEffect(() => { if (params.step === "reconnect" && params.id) { - manualSyncTransactions.execute({ + reconnectConnection.execute({ connectionId: params.id, provider: connection.provider, - type: "reconnect", }); } }, [params]); @@ -215,8 +236,6 @@ export function BankConnection({ connection }: BankConnectionProps) { const handleManualSync = () => { manualSyncTransactions.execute({ connectionId: connection.id, - provider: connection.provider, - type: "sync", }); }; diff --git a/packages/events/src/events.ts b/packages/events/src/events.ts index 41242c790b..64759fa2e9 100644 --- a/packages/events/src/events.ts +++ b/packages/events/src/events.ts @@ -255,4 +255,8 @@ export const LogEvents = { name: "Create Customer Tag", channel: "customer", }, + ReconnectConnection: { + name: "Reconnect Connection", + channel: "bank", + }, };