import { mutation, query } from "./_generated/server"; import { v } from "convex/values"; import { getAuthUserId } from "@convex-dev/auth/server"; const SECTION_KEYS = [ "profile", "features", "competitors", "keywords", "problems", "personas", "useCases", "dorkQueries", ] as const; function assertSectionKey(sectionKey: string) { if (!SECTION_KEYS.includes(sectionKey as any)) { throw new Error(`Invalid section key: ${sectionKey}`); } } async function getOwnedAnalysis(ctx: any, analysisId: any) { const userId = await getAuthUserId(ctx); if (!userId) throw new Error("Unauthorized"); const analysis = await ctx.db.get(analysisId); if (!analysis) throw new Error("Analysis not found"); const project = await ctx.db.get(analysis.projectId); if (!project || project.userId !== userId) { throw new Error("Project not found or unauthorized"); } return analysis; } async function patchAnalysisFromSection( ctx: any, analysisId: any, sectionKey: string, items: any ) { if (sectionKey === "profile" && items && typeof items === "object") { const patch: Record = {}; if (typeof items.productName === "string") patch.productName = items.productName; if (typeof items.tagline === "string") patch.tagline = items.tagline; if (typeof items.description === "string") patch.description = items.description; if (typeof items.category === "string") patch.category = items.category; if (typeof items.positioning === "string") patch.positioning = items.positioning; if (Object.keys(patch).length > 0) { await ctx.db.patch(analysisId, patch); } return; } if (sectionKey === "features") { await ctx.db.patch(analysisId, { features: items }); return; } if (sectionKey === "competitors") { await ctx.db.patch(analysisId, { competitors: items }); return; } if (sectionKey === "keywords") { await ctx.db.patch(analysisId, { keywords: items }); return; } if (sectionKey === "problems") { await ctx.db.patch(analysisId, { problemsSolved: items }); return; } if (sectionKey === "personas") { await ctx.db.patch(analysisId, { personas: items }); return; } if (sectionKey === "useCases") { await ctx.db.patch(analysisId, { useCases: items }); return; } if (sectionKey === "dorkQueries") { await ctx.db.patch(analysisId, { dorkQueries: items }); } } export const listByAnalysis = query({ args: { analysisId: v.id("analyses") }, handler: async (ctx, args) => { await getOwnedAnalysis(ctx, args.analysisId); return await ctx.db .query("analysisSections") .withIndex("by_analysis", (q) => q.eq("analysisId", args.analysisId)) .collect(); }, }); export const getSection = query({ args: { analysisId: v.id("analyses"), sectionKey: v.string() }, handler: async (ctx, args) => { await getOwnedAnalysis(ctx, args.analysisId); assertSectionKey(args.sectionKey); return await ctx.db .query("analysisSections") .withIndex("by_analysis_section", (q) => q.eq("analysisId", args.analysisId).eq("sectionKey", args.sectionKey) ) .first(); }, }); export const replaceSection = mutation({ args: { analysisId: v.id("analyses"), sectionKey: v.string(), items: v.any(), lastPrompt: v.optional(v.string()), source: v.union(v.literal("ai"), v.literal("manual"), v.literal("mixed")), }, handler: async (ctx, args) => { await getOwnedAnalysis(ctx, args.analysisId); assertSectionKey(args.sectionKey); const existing = await ctx.db .query("analysisSections") .withIndex("by_analysis_section", (q) => q.eq("analysisId", args.analysisId).eq("sectionKey", args.sectionKey) ) .first(); if (existing) { await ctx.db.patch(existing._id, { items: args.items, lastPrompt: args.lastPrompt, source: args.source, updatedAt: Date.now(), }); } else { await ctx.db.insert("analysisSections", { analysisId: args.analysisId, sectionKey: args.sectionKey, items: args.items, lastPrompt: args.lastPrompt, source: args.source, updatedAt: Date.now(), }); } await patchAnalysisFromSection(ctx, args.analysisId, args.sectionKey, args.items); return { success: true }; }, }); export const addItem = mutation({ args: { analysisId: v.id("analyses"), sectionKey: v.string(), item: v.any(), }, handler: async (ctx, args) => { await getOwnedAnalysis(ctx, args.analysisId); assertSectionKey(args.sectionKey); const section = await ctx.db .query("analysisSections") .withIndex("by_analysis_section", (q) => q.eq("analysisId", args.analysisId).eq("sectionKey", args.sectionKey) ) .first(); if (!section || !Array.isArray(section.items)) { throw new Error("Section is not editable as a list."); } const updated = [...section.items, args.item]; await ctx.db.patch(section._id, { items: updated, source: "mixed", updatedAt: Date.now(), }); await patchAnalysisFromSection(ctx, args.analysisId, args.sectionKey, updated); return { success: true }; }, }); export const removeItem = mutation({ args: { analysisId: v.id("analyses"), sectionKey: v.string(), index: v.number(), }, handler: async (ctx, args) => { await getOwnedAnalysis(ctx, args.analysisId); assertSectionKey(args.sectionKey); const section = await ctx.db .query("analysisSections") .withIndex("by_analysis_section", (q) => q.eq("analysisId", args.analysisId).eq("sectionKey", args.sectionKey) ) .first(); if (!section || !Array.isArray(section.items)) { throw new Error("Section is not editable as a list."); } const updated = section.items.filter((_: any, idx: number) => idx !== args.index); await ctx.db.patch(section._id, { items: updated, source: "mixed", updatedAt: Date.now(), }); await patchAnalysisFromSection(ctx, args.analysisId, args.sectionKey, updated); return { success: true }; }, });