feat: query plaid transactions

This commit is contained in:
Max Koon
2025-10-15 10:53:37 -04:00
parent 23987e4f87
commit 415150d58e
12 changed files with 322 additions and 32 deletions

View File

@@ -1,5 +1,6 @@
import {
type ReadonlyJSONValue,
type Transaction,
withValidation,
} from "@rocicorp/zero";
import {
@@ -11,13 +12,31 @@ import { PostgresJSConnection } from '@rocicorp/zero/pg';
import postgres from 'postgres';
import {
createMutators as createMutatorsShared,
isLoggedIn,
queries,
schema,
type Mutators,
type Schema,
} from "@money/shared";
import type { AuthData } from "@money/shared/auth";
import { getHono } from "./hono";
import { Configuration, CountryCode, PlaidApi, PlaidEnvironments, Products } from "plaid";
import { randomUUID } from "crypto";
import { db } from "./db";
import { plaidAccessTokens, plaidLink, transaction } from "@money/shared/db";
import { eq } from "drizzle-orm";
const configuration = new Configuration({
basePath: PlaidEnvironments.production,
baseOptions: {
headers: {
'PLAID-CLIENT-ID': process.env.PLAID_CLIENT_ID,
'PLAID-SECRET': process.env.PLAID_SECRET,
}
}
});
const plaidClient = new PlaidApi(configuration);
const processor = new PushProcessor(
@@ -27,6 +46,8 @@ const processor = new PushProcessor(
),
);
type Tx = Transaction<Schema>;
const createMutators = (authData: AuthData | null) => {
const mutators = createMutatorsShared(authData);
return {
@@ -34,7 +55,73 @@ const createMutators = (authData: AuthData | null) => {
link: {
...mutators.link,
async create() {
console.log("Here is my function running on the server!!!");
isLoggedIn(authData);
console.log("Creating Link token");
const r = await plaidClient.linkTokenCreate({
user: {
client_user_id: authData.user.id,
},
client_name: "Koon Money",
language: "en",
products: [Products.Transactions],
country_codes: [CountryCode.Us],
hosted_link: {}
});
console.log("Result", r);
const { link_token, hosted_link_url } = r.data;
if (!hosted_link_url) throw Error("No link in response");
await db.insert(plaidLink).values({
id: randomUUID() as string,
user_id: authData.user.id,
link: hosted_link_url,
token: link_token,
});
},
async get(_, { link_token }) {
isLoggedIn(authData);
const linkResp = await plaidClient.linkTokenGet({
link_token,
});
if (!linkResp) throw Error("No link respo");
console.log(JSON.stringify(linkResp.data, null, 4));
const publicToken = linkResp.data.link_sessions?.at(0)?.results?.item_add_results.at(0)?.public_token;
if (!publicToken) throw Error("No public token");
const { data } = await plaidClient.itemPublicTokenExchange({
public_token: publicToken,
})
await db.insert(plaidAccessTokens).values({
id: randomUUID(),
userId: authData.user.id,
token: data.access_token,
});
},
async updateTransactions() {
isLoggedIn(authData);
const accessToken = await db.query.plaidAccessTokens.findFirst({
where: eq(plaidAccessTokens.userId, authData.user.id)
});
if (!accessToken) throw Error("No plaid account");
const { data } = await plaidClient.transactionsGet({
access_token: accessToken.token,
start_date: "2025-10-01",
end_date: "2025-10-15",
});
for (const t of data.transactions) {
await db.insert(transaction).values({
id: randomUUID(),
user_id: authData.user.id,
name: t.name,
amount: t.amount,
});
}
}
}
} as const satisfies Mutators;