170 lines
5.2 KiB
TypeScript
170 lines
5.2 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"),
|
|
);
|
|
|
|
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_dedup", ["userId", "dedupHash"])
|
|
.index("by_user_extref", ["userId", "externalRef"]),
|
|
|
|
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(),
|
|
monthlyPayment: v.optional(v.number()),
|
|
termMonths: v.optional(v.number()),
|
|
startDate: v.string(),
|
|
currentBalance: 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"]),
|
|
});
|