import { mutation, query } from "./_generated/server"; import { v } from "convex/values"; import { getAuthUserId } from "@convex-dev/auth/server"; export const getProjectDataSources = query({ args: { projectId: v.id("projects") }, handler: async (ctx, args) => { const userId = await getAuthUserId(ctx); if (!userId) return []; // Verify project ownership const project = await ctx.db.get(args.projectId); if (!project || project.userId !== userId) return []; return await ctx.db .query("dataSources") .filter((q) => q.eq(q.field("projectId"), args.projectId)) .collect(); }, }); export const getById = query({ args: { dataSourceId: v.id("dataSources") }, handler: async (ctx, args) => { const userId = await getAuthUserId(ctx); if (!userId) return null; const dataSource = await ctx.db.get(args.dataSourceId); if (!dataSource) return null; const project = await ctx.db.get(dataSource.projectId); if (!project || project.userId !== userId) return null; return dataSource; }, }); export const addDataSource = mutation({ args: { projectId: v.optional(v.id("projects")), // Optional, if not provided, use default url: v.string(), name: v.string(), type: v.literal("website"), }, handler: async (ctx, args) => { const userId = await getAuthUserId(ctx); if (!userId) throw new Error("Unauthorized"); let normalizedUrl = args.url.trim(); if (normalizedUrl.startsWith("manual:")) { // Keep manual sources as-is. } else if (!normalizedUrl.startsWith("http")) { normalizedUrl = `https://${normalizedUrl}`; } normalizedUrl = normalizedUrl.replace(/\/+$/, ""); let projectId = args.projectId; // Use default project if not provided if (!projectId) { const defaultProject = await ctx.db .query("projects") .filter((q) => q.and(q.eq(q.field("userId"), userId), q.eq(q.field("isDefault"), true))) .first(); if (!defaultProject) { // Create a default project if none exists (First onboarding) projectId = await ctx.db.insert("projects", { userId, name: "My Project", isDefault: true, dorkingConfig: { selectedSourceIds: [] }, }); } else { projectId = defaultProject._id; } } const existing = await ctx.db .query("dataSources") .withIndex("by_project_url", (q) => q.eq("projectId", projectId!).eq("url", normalizedUrl) ) .first(); const sourceId = existing ? existing._id : await ctx.db.insert("dataSources", { projectId: projectId!, // Assert exists type: args.type, url: normalizedUrl, name: args.name, analysisStatus: "pending", lastAnalyzedAt: undefined, lastError: undefined, // analysisResults not set initially }); // Auto-select this source in the project config const project = await ctx.db.get(projectId!); if (project) { const currentSelected = project.dorkingConfig.selectedSourceIds; if (!currentSelected.includes(sourceId)) { await ctx.db.patch(projectId!, { dorkingConfig: { selectedSourceIds: [...currentSelected, sourceId] } }); } } return { sourceId, projectId: projectId!, isExisting: Boolean(existing) }; }, }); export const updateDataSourceStatus = mutation({ args: { dataSourceId: v.id("dataSources"), analysisStatus: v.union( v.literal("pending"), v.literal("completed"), v.literal("failed") ), lastError: v.optional(v.string()), lastAnalyzedAt: v.optional(v.number()), }, handler: async (ctx, args) => { const userId = await getAuthUserId(ctx); if (!userId) throw new Error("Unauthorized"); const dataSource = await ctx.db.get(args.dataSourceId); if (!dataSource) throw new Error("Data source not found"); const project = await ctx.db.get(dataSource.projectId); if (!project || project.userId !== userId) { throw new Error("Project not found or unauthorized"); } await ctx.db.patch(args.dataSourceId, { analysisStatus: args.analysisStatus, lastError: args.lastError, lastAnalyzedAt: args.lastAnalyzedAt, }); }, }); export const remove = mutation({ args: { dataSourceId: v.id("dataSources") }, handler: async (ctx, args) => { const userId = await getAuthUserId(ctx); if (!userId) throw new Error("Unauthorized"); const dataSource = await ctx.db.get(args.dataSourceId); if (!dataSource) throw new Error("Data source not found"); const project = await ctx.db.get(dataSource.projectId); if (!project || project.userId !== userId) { throw new Error("Project not found or unauthorized"); } const updatedSelected = project.dorkingConfig.selectedSourceIds.filter( (id) => id !== args.dataSourceId ); await ctx.db.patch(project._id, { dorkingConfig: { selectedSourceIds: updatedSelected }, }); const analyses = await ctx.db .query("analyses") .withIndex("by_dataSource_createdAt", (q) => q.eq("dataSourceId", args.dataSourceId) ) .collect(); for (const analysis of analyses) { const sections = await ctx.db .query("analysisSections") .withIndex("by_analysis", (q) => q.eq("analysisId", analysis._id)) .collect(); for (const section of sections) { await ctx.db.delete(section._id); } await ctx.db.delete(analysis._id); } const analysisJobs = await ctx.db .query("analysisJobs") .filter((q) => q.eq(q.field("dataSourceId"), args.dataSourceId)) .collect(); for (const job of analysisJobs) { await ctx.db.delete(job._id); } await ctx.db.delete(args.dataSourceId); }, });