feat: add guarded savings agent tools
This commit is contained in:
131
convex/bank/balances.ts
Normal file
131
convex/bank/balances.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
import { action, query } from "../_generated/server";
|
||||
import { v } from "convex/values";
|
||||
import { internal } from "../_generated/api";
|
||||
import { requireUserId } from "../lib/helpers";
|
||||
import { getAuthUserId } from "@convex-dev/auth/server";
|
||||
import type { Doc, Id } from "../_generated/dataModel";
|
||||
|
||||
const balanceProvider = v.union(v.literal("comdirect"), v.literal("fints"));
|
||||
const balanceStatus = v.union(
|
||||
v.literal("fresh"),
|
||||
v.literal("stale"),
|
||||
v.literal("error"),
|
||||
v.literal("missing"),
|
||||
);
|
||||
|
||||
export const listLatest = query({
|
||||
args: {
|
||||
includeArchived: v.optional(v.boolean()),
|
||||
},
|
||||
returns: v.array(
|
||||
v.object({
|
||||
accountId: v.id("accounts"),
|
||||
accountName: v.string(),
|
||||
accountType: v.string(),
|
||||
iban: v.optional(v.string()),
|
||||
externalId: v.optional(v.string()),
|
||||
balance: v.union(v.number(), v.null()),
|
||||
currency: v.string(),
|
||||
provider: v.union(balanceProvider, v.null()),
|
||||
fetchedAt: v.union(v.number(), v.null()),
|
||||
asOf: v.optional(v.string()),
|
||||
status: balanceStatus,
|
||||
errorMessage: v.optional(v.string()),
|
||||
}),
|
||||
),
|
||||
handler: async (ctx, args) => {
|
||||
const userId = await requireUserId(ctx);
|
||||
const accounts = await ctx.db
|
||||
.query("accounts")
|
||||
.withIndex("by_user", (q) => q.eq("userId", userId))
|
||||
.collect();
|
||||
const visibleAccounts = accounts
|
||||
.filter((account) => args.includeArchived || !account.isArchived)
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
const balances = await ctx.db
|
||||
.query("accountBalances")
|
||||
.withIndex("by_user", (q) => q.eq("userId", userId))
|
||||
.collect();
|
||||
const latestByAccount = new Map<Id<"accounts">, Doc<"accountBalances">>();
|
||||
for (const balance of balances.sort((a, b) => b.fetchedAt - a.fetchedAt)) {
|
||||
if (!latestByAccount.has(balance.accountId)) {
|
||||
latestByAccount.set(balance.accountId, balance);
|
||||
}
|
||||
}
|
||||
|
||||
return visibleAccounts.map((account) => {
|
||||
const latest = latestByAccount.get(account._id);
|
||||
if (!latest) {
|
||||
return {
|
||||
accountId: account._id,
|
||||
accountName: account.name,
|
||||
accountType: account.type,
|
||||
iban: account.iban,
|
||||
externalId: account.externalId,
|
||||
balance: null,
|
||||
currency: account.currency,
|
||||
provider: null,
|
||||
fetchedAt: null,
|
||||
status: "missing" as const,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
accountId: account._id,
|
||||
accountName: account.name,
|
||||
accountType: account.type,
|
||||
iban: account.iban,
|
||||
externalId: account.externalId ?? latest.externalId,
|
||||
balance: latest.balance,
|
||||
currency: latest.currency,
|
||||
provider: latest.provider,
|
||||
fetchedAt: latest.fetchedAt,
|
||||
asOf: latest.asOf,
|
||||
status: latest.status,
|
||||
errorMessage: latest.errorMessage,
|
||||
};
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const refresh = action({
|
||||
args: {
|
||||
accountId: v.optional(v.id("accounts")),
|
||||
pin: v.optional(v.string()),
|
||||
},
|
||||
returns: v.object({
|
||||
updatedCount: v.number(),
|
||||
provider: balanceProvider,
|
||||
awaitingTan: v.boolean(),
|
||||
errors: v.array(
|
||||
v.object({
|
||||
accountId: v.optional(v.id("accounts")),
|
||||
externalId: v.optional(v.string()),
|
||||
message: v.string(),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
handler: async (
|
||||
ctx,
|
||||
args,
|
||||
): Promise<{
|
||||
updatedCount: number;
|
||||
provider: "comdirect" | "fints";
|
||||
awaitingTan: boolean;
|
||||
errors: Array<{
|
||||
accountId?: Id<"accounts">;
|
||||
externalId?: string;
|
||||
message: string;
|
||||
}>;
|
||||
}> => {
|
||||
const userId = await getAuthUserId(ctx);
|
||||
if (!userId) throw new Error("Nicht angemeldet");
|
||||
|
||||
return await ctx.runAction(internal.bank.orchestrator.refreshBalancesInternal, {
|
||||
userId,
|
||||
accountId: args.accountId,
|
||||
pin: args.pin,
|
||||
});
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user