Files
SanatiLeads/convex/analysisSections.ts
2026-02-04 01:05:00 +00:00

215 lines
6.1 KiB
TypeScript

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<string, any> = {};
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 };
},
});