initial commit
This commit is contained in:
158
convex/categories.ts
Normal file
158
convex/categories.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
import { query, mutation } from "./_generated/server";
|
||||
import { v } from "convex/values";
|
||||
import { assertOwned, requireUserId } from "./lib/helpers";
|
||||
import { DEFAULT_CATEGORIES } from "./lib/seedCategories";
|
||||
|
||||
const categoryValidator = v.object({
|
||||
_id: v.id("categories"),
|
||||
_creationTime: v.number(),
|
||||
userId: v.id("users"),
|
||||
name: v.string(),
|
||||
kind: v.union(v.literal("einnahme"), v.literal("ausgabe")),
|
||||
block: v.optional(v.union(v.literal("wiederkehrend"), v.literal("variabel"))),
|
||||
color: v.string(),
|
||||
icon: v.optional(v.string()),
|
||||
sortOrder: v.number(),
|
||||
isSystem: v.boolean(),
|
||||
});
|
||||
|
||||
export const list = query({
|
||||
args: {},
|
||||
returns: v.array(categoryValidator),
|
||||
handler: async (ctx) => {
|
||||
const userId = await requireUserId(ctx);
|
||||
const categories = await ctx.db
|
||||
.query("categories")
|
||||
.withIndex("by_user", (q) => q.eq("userId", userId))
|
||||
.collect();
|
||||
return categories.sort((a, b) => a.sortOrder - b.sortOrder);
|
||||
},
|
||||
});
|
||||
|
||||
export const create = mutation({
|
||||
args: {
|
||||
name: v.string(),
|
||||
kind: v.union(v.literal("einnahme"), v.literal("ausgabe")),
|
||||
block: v.optional(v.union(v.literal("wiederkehrend"), v.literal("variabel"))),
|
||||
color: v.string(),
|
||||
icon: v.optional(v.string()),
|
||||
sortOrder: v.number(),
|
||||
},
|
||||
returns: v.id("categories"),
|
||||
handler: async (ctx, args) => {
|
||||
const userId = await requireUserId(ctx);
|
||||
if (args.kind === "ausgabe" && !args.block) {
|
||||
throw new Error("Block ist bei Ausgaben Pflicht");
|
||||
}
|
||||
const existing = await ctx.db
|
||||
.query("categories")
|
||||
.withIndex("by_user_name", (q) => q.eq("userId", userId).eq("name", args.name))
|
||||
.unique();
|
||||
if (existing) throw new Error("Kategorie existiert bereits");
|
||||
|
||||
return await ctx.db.insert("categories", {
|
||||
userId,
|
||||
name: args.name,
|
||||
kind: args.kind,
|
||||
block: args.kind === "ausgabe" ? args.block : undefined,
|
||||
color: args.color,
|
||||
icon: args.icon,
|
||||
sortOrder: args.sortOrder,
|
||||
isSystem: false,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const update = mutation({
|
||||
args: {
|
||||
id: v.id("categories"),
|
||||
name: v.optional(v.string()),
|
||||
kind: v.optional(v.union(v.literal("einnahme"), v.literal("ausgabe"))),
|
||||
block: v.optional(v.union(v.literal("wiederkehrend"), v.literal("variabel"))),
|
||||
color: v.optional(v.string()),
|
||||
icon: v.optional(v.string()),
|
||||
sortOrder: v.optional(v.number()),
|
||||
},
|
||||
returns: v.null(),
|
||||
handler: async (ctx, args) => {
|
||||
const userId = await requireUserId(ctx);
|
||||
const category = await assertOwned(
|
||||
await ctx.db.get("categories", args.id),
|
||||
userId,
|
||||
"Kategorie",
|
||||
);
|
||||
const { id, ...updates } = args;
|
||||
const patch: Record<string, unknown> = {};
|
||||
for (const [key, value] of Object.entries(updates)) {
|
||||
if (value !== undefined) patch[key] = value;
|
||||
}
|
||||
const kind = (patch.kind as typeof category.kind | undefined) ?? category.kind;
|
||||
const block = (patch.block as typeof category.block | undefined) ?? category.block;
|
||||
if (kind === "ausgabe" && !block) {
|
||||
throw new Error("Block ist bei Ausgaben Pflicht");
|
||||
}
|
||||
if (kind === "einnahme") {
|
||||
patch.block = undefined;
|
||||
}
|
||||
if (Object.keys(patch).length > 0) {
|
||||
await ctx.db.patch(id, patch);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
export const remove = mutation({
|
||||
args: { id: v.id("categories") },
|
||||
returns: v.null(),
|
||||
handler: async (ctx, args) => {
|
||||
const userId = await requireUserId(ctx);
|
||||
const category = await assertOwned(
|
||||
await ctx.db.get("categories", args.id),
|
||||
userId,
|
||||
"Kategorie",
|
||||
);
|
||||
if (category.isSystem) {
|
||||
throw new Error("System-Kategorien können nicht gelöscht werden");
|
||||
}
|
||||
|
||||
const txs = await ctx.db
|
||||
.query("transactions")
|
||||
.withIndex("by_user_category", (q) =>
|
||||
q.eq("userId", userId).eq("categoryId", args.id),
|
||||
)
|
||||
.collect();
|
||||
for (const tx of txs) {
|
||||
await ctx.db.patch(tx._id, { categoryId: undefined });
|
||||
}
|
||||
|
||||
await ctx.db.delete(args.id);
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
export const seedDefaults = mutation({
|
||||
args: {},
|
||||
returns: v.null(),
|
||||
handler: async (ctx) => {
|
||||
const userId = await requireUserId(ctx);
|
||||
const existing = await ctx.db
|
||||
.query("categories")
|
||||
.withIndex("by_user", (q) => q.eq("userId", userId))
|
||||
.first();
|
||||
if (existing) return null;
|
||||
|
||||
for (const cat of DEFAULT_CATEGORIES) {
|
||||
await ctx.db.insert("categories", {
|
||||
userId,
|
||||
name: cat.name,
|
||||
kind: cat.kind,
|
||||
block: cat.block,
|
||||
color: cat.color,
|
||||
icon: cat.icon,
|
||||
sortOrder: cat.sortOrder,
|
||||
isSystem: true,
|
||||
});
|
||||
}
|
||||
return null;
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user