import { mutation, query } from "./_generated/server"; import { v } from "convex/values"; import { getAuthUserId } from "@convex-dev/auth/server"; export const getLatestByProject = query({ args: { projectId: v.id("projects") }, handler: async (ctx, { projectId }) => { const userId = await getAuthUserId(ctx); if (!userId) return null; const project = await ctx.db.get(projectId); if (!project || project.userId !== userId) return null; return await ctx.db .query("analyses") .withIndex("by_project_createdAt", (q) => q.eq("projectId", projectId)) .order("desc") .first(); }, }); export const getLatestByDataSource = query({ args: { dataSourceId: v.id("dataSources") }, handler: async (ctx, { dataSourceId }) => { const userId = await getAuthUserId(ctx); if (!userId) return null; const dataSource = await ctx.db.get(dataSourceId); if (!dataSource) return null; const project = await ctx.db.get(dataSource.projectId); if (!project || project.userId !== userId) return null; return await ctx.db .query("analyses") .withIndex("by_dataSource_createdAt", (q) => q.eq("dataSourceId", dataSourceId) ) .order("desc") .first(); }, }); export const createAnalysis = mutation({ args: { projectId: v.id("projects"), dataSourceId: v.id("dataSources"), analysis: v.object({ productName: v.string(), tagline: v.string(), description: v.string(), category: v.string(), positioning: v.string(), features: v.array(v.object({ name: v.string(), description: v.string(), benefits: v.array(v.string()), useCases: v.array(v.string()), })), problemsSolved: v.array(v.object({ problem: v.string(), severity: v.union(v.literal("high"), v.literal("medium"), v.literal("low")), currentWorkarounds: v.array(v.string()), emotionalImpact: v.string(), searchTerms: v.array(v.string()), })), personas: v.array(v.object({ name: v.string(), role: v.string(), companySize: v.string(), industry: v.string(), painPoints: v.array(v.string()), goals: v.array(v.string()), techSavvy: v.union(v.literal("low"), v.literal("medium"), v.literal("high")), objections: v.array(v.string()), searchBehavior: v.array(v.string()), })), keywords: v.array(v.object({ term: v.string(), type: v.union( v.literal("product"), v.literal("problem"), v.literal("solution"), v.literal("competitor"), v.literal("feature"), v.literal("longtail"), v.literal("differentiator") ), searchVolume: v.union(v.literal("high"), v.literal("medium"), v.literal("low")), intent: v.union(v.literal("informational"), v.literal("navigational"), v.literal("transactional")), funnel: v.union(v.literal("awareness"), v.literal("consideration"), v.literal("decision")), emotionalIntensity: v.union(v.literal("frustrated"), v.literal("curious"), v.literal("ready")), })), useCases: v.array(v.object({ scenario: v.string(), trigger: v.string(), emotionalState: v.string(), currentWorkflow: v.array(v.string()), desiredOutcome: v.string(), alternativeProducts: v.array(v.string()), whyThisProduct: v.string(), churnRisk: v.array(v.string()), })), competitors: v.array(v.object({ name: v.string(), differentiator: v.string(), theirStrength: v.string(), switchTrigger: v.string(), theirWeakness: v.string(), })), dorkQueries: v.array(v.object({ query: v.string(), platform: v.union( v.literal("reddit"), v.literal("hackernews"), v.literal("indiehackers"), v.literal("twitter"), v.literal("quora"), v.literal("stackoverflow") ), intent: v.union( v.literal("looking-for"), v.literal("frustrated"), v.literal("alternative"), v.literal("comparison"), v.literal("problem-solving"), v.literal("tutorial") ), priority: v.union(v.literal("high"), v.literal("medium"), v.literal("low")), })), scrapedAt: v.string(), analysisVersion: v.string(), }), }, handler: async (ctx, args) => { const userId = await getAuthUserId(ctx); if (!userId) throw new Error("Unauthorized"); const project = await ctx.db.get(args.projectId); if (!project || project.userId !== userId) { throw new Error("Project not found or unauthorized"); } return await ctx.db.insert("analyses", { projectId: args.projectId, dataSourceId: args.dataSourceId, createdAt: Date.now(), analysisVersion: args.analysis.analysisVersion, productName: args.analysis.productName, tagline: args.analysis.tagline, description: args.analysis.description, category: args.analysis.category, positioning: args.analysis.positioning, features: args.analysis.features, problemsSolved: args.analysis.problemsSolved, personas: args.analysis.personas, keywords: args.analysis.keywords, useCases: args.analysis.useCases, competitors: args.analysis.competitors, dorkQueries: args.analysis.dorkQueries, scrapedAt: args.analysis.scrapedAt, }); }, });