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

194 lines
6.5 KiB
TypeScript

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);
},
});