Files
finanzen/convex/schema.ts

206 lines
6.5 KiB
TypeScript

import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
import { authTables } from "@convex-dev/auth/server";
const categoryKind = v.union(v.literal("einnahme"), v.literal("ausgabe"));
const expenseBlock = v.union(v.literal("wiederkehrend"), v.literal("variabel"));
const loanStatus = v.union(
v.literal("aktiv"),
v.literal("abbezahlt"),
v.literal("pausiert"),
);
const chatRole = v.union(v.literal("user"), v.literal("assistant"));
const chatToolTrace = v.object({
name: v.string(),
inputSummary: v.string(),
resultSummary: v.string(),
});
export default defineSchema({
...authTables,
accounts: defineTable({
userId: v.id("users"),
name: v.string(),
type: v.string(),
iban: v.optional(v.string()),
openingBalance: v.number(),
currency: v.string(),
isArchived: v.boolean(),
externalId: v.optional(v.string()),
})
.index("by_user", ["userId"])
.index("by_user_external", ["userId", "externalId"]),
categories: defineTable({
userId: v.id("users"),
name: v.string(),
kind: categoryKind,
block: v.optional(expenseBlock),
color: v.string(),
icon: v.optional(v.string()),
sortOrder: v.number(),
isSystem: v.boolean(),
})
.index("by_user", ["userId"])
.index("by_user_kind", ["userId", "kind"])
.index("by_user_name", ["userId", "name"]),
transactions: defineTable({
userId: v.id("users"),
accountId: v.optional(v.id("accounts")),
categoryId: v.optional(v.id("categories")),
bookingDate: v.optional(v.string()),
valueDate: v.optional(v.string()),
description: v.string(),
counterparty: v.optional(v.string()),
amount: v.number(),
vorgang: v.optional(v.string()),
isPending: v.boolean(),
notes: v.optional(v.string()),
rawText: v.optional(v.string()),
importId: v.optional(v.id("imports")),
assignedMonth: v.optional(v.string()),
effectiveMonth: v.optional(v.string()),
dedupHash: v.optional(v.string()),
externalRef: v.optional(v.string()),
})
.index("by_user", ["userId"])
.index("by_user_booking", ["userId", "bookingDate"])
.index("by_user_effmonth", ["userId", "effectiveMonth"])
.index("by_user_category", ["userId", "categoryId"])
.index("by_user_account", ["userId", "accountId"])
.index("by_user_account_booking", ["userId", "accountId", "bookingDate"])
.index("by_user_account_effmonth", ["userId", "accountId", "effectiveMonth"])
.index("by_user_dedup", ["userId", "dedupHash"])
.index("by_user_extref", ["userId", "externalRef"])
.searchIndex("search_description", {
searchField: "description",
filterFields: ["userId"],
}),
loans: defineTable({
userId: v.id("users"),
name: v.string(),
lender: v.optional(v.string()),
accountId: v.optional(v.id("accounts")),
categoryId: v.optional(v.id("categories")),
principal: v.number(),
annualInterestRate: v.number(),
effectiveAnnualRate: v.optional(v.number()),
monthlyPayment: v.optional(v.number()),
termMonths: v.optional(v.number()),
startDate: v.string(),
currentBalance: v.optional(v.number()),
totalInterest: v.optional(v.number()),
totalAmount: v.optional(v.number()),
status: loanStatus,
notes: v.optional(v.string()),
})
.index("by_user", ["userId"])
.index("by_user_status", ["userId", "status"]),
imports: defineTable({
userId: v.id("users"),
filename: v.string(),
source: v.string(),
accountId: v.optional(v.id("accounts")),
rowCount: v.number(),
importedCount: v.number(),
status: v.string(),
}).index("by_user", ["userId"]),
appSettings: defineTable({
userId: v.id("users"),
ownNames: v.array(v.string()),
monthBasis: v.union(v.literal("effective"), v.literal("booking")),
salaryShift: v.object({
enabled: v.boolean(),
categoryNames: v.array(v.string()),
dayThreshold: v.number(),
}),
}).index("by_user", ["userId"]),
comdirectSessions: defineTable({
userId: v.id("users"),
sessionUuid: v.string(),
identifier: v.optional(v.string()),
accessToken: v.optional(v.string()),
refreshToken: v.optional(v.string()),
secondaryActive: v.boolean(),
challengeId: v.optional(v.string()),
challengeType: v.optional(v.string()),
status: v.string(),
expiresAt: v.optional(v.number()),
}).index("by_user", ["userId"]),
bankConfig: defineTable({
userId: v.id("users"),
providerPreference: v.union(
v.literal("auto"),
v.literal("comdirect"),
v.literal("fints"),
),
comdirectHasCredentials: v.boolean(),
fints: v.object({
blz: v.string(),
url: v.string(),
login: v.string(),
productId: v.string(),
productVersion: v.optional(v.string()),
tanMethodId: v.optional(v.number()),
tanMediaName: v.optional(v.string()),
bankingInformationJson: v.optional(v.string()),
}),
}).index("by_user", ["userId"]),
syncState: defineTable({
userId: v.id("users"),
lastSync: v.optional(v.number()),
lastProviderUsed: v.optional(v.union(v.literal("comdirect"), v.literal("fints"))),
lastError: v.optional(v.string()),
}).index("by_user", ["userId"]),
pendingTan: defineTable({
userId: v.id("users"),
status: v.union(
v.literal("idle"),
v.literal("awaiting"),
v.literal("done"),
v.literal("error"),
),
challengeRef: v.optional(v.string()),
challengeMessage: v.optional(v.string()),
photoTanMimeType: v.optional(v.string()),
photoTanBase64: v.optional(v.string()),
createdAt: v.number(),
updatedAt: v.number(),
errorMessage: v.optional(v.string()),
pollAttempt: v.optional(v.number()),
syncJobJson: v.optional(v.string()),
isDecoupled: v.optional(v.boolean()),
submittedTan: v.optional(v.string()),
}).index("by_user", ["userId"]),
chatSessions: defineTable({
userId: v.id("users"),
title: v.string(),
createdAt: v.number(),
updatedAt: v.number(),
messageCount: v.number(),
legacyLocalId: v.optional(v.string()),
isDeleted: v.boolean(),
})
.index("by_user_deleted_updated", ["userId", "isDeleted", "updatedAt"])
.index("by_user_legacyLocalId", ["userId", "legacyLocalId"]),
chatMessages: defineTable({
userId: v.id("users"),
sessionId: v.id("chatSessions"),
role: chatRole,
content: v.string(),
createdAt: v.number(),
toolTrace: v.optional(v.array(chatToolTrace)),
}).index("by_user_session_created", ["userId", "sessionId", "createdAt"]),
});