215 lines
6.1 KiB
TypeScript
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 };
|
|
},
|
|
});
|