Files
2026-06-22 10:37:26 -04:00

115 lines
3.5 KiB
TypeScript

import { v } from 'convex/values';
import type { Doc, Id } from './_generated/dataModel';
import { internalMutation, query } from './_generated/server';
import { getOwnedSpoon, getRequiredUserId } from './model';
const syncKind = v.union(
v.literal('scheduled_check'),
v.literal('manual_check'),
v.literal('upstream_update'),
v.literal('merge_attempt'),
v.literal('ai_review'),
);
const syncStatus = v.union(
v.literal('queued'),
v.literal('running'),
v.literal('clean'),
v.literal('conflict'),
v.literal('needs_review'),
v.literal('failed'),
v.literal('merged'),
);
const syncDecision = v.union(
v.literal('auto_synced'),
v.literal('thread_created'),
v.literal('ignored'),
v.literal('failed'),
);
export const listRecent = query({
args: { limit: v.optional(v.number()) },
handler: async (ctx, { limit }) => {
const ownerId = await getRequiredUserId(ctx);
const runs = await ctx.db
.query('syncRuns')
.withIndex('by_owner', (q) => q.eq('ownerId', ownerId))
.order('desc')
.take(limit ?? 25);
return runs;
},
});
export const listForSpoon = query({
args: { spoonId: v.id('spoons'), limit: v.optional(v.number()) },
handler: async (ctx, { spoonId, limit }) => {
const ownerId = await getRequiredUserId(ctx);
await getOwnedSpoon(ctx, spoonId, ownerId);
return await ctx.db
.query('syncRuns')
.withIndex('by_spoon', (q) => q.eq('spoonId', spoonId))
.order('desc')
.take(limit ?? 25);
},
});
export const createInternal = internalMutation({
args: {
spoonId: v.id('spoons'),
ownerId: v.id('users'),
threadId: v.optional(v.id('threads')),
kind: syncKind,
status: syncStatus,
upstreamFrom: v.optional(v.string()),
upstreamTo: v.optional(v.string()),
summary: v.optional(v.string()),
aiAssessment: v.optional(v.string()),
mergeRequestUrl: v.optional(v.string()),
error: v.optional(v.string()),
decision: v.optional(syncDecision),
},
handler: async (ctx, args): Promise<Id<'syncRuns'>> => {
const now = Date.now();
return await ctx.db.insert('syncRuns', {
...args,
createdAt: now,
updatedAt: now,
});
},
});
export const patchInternal = internalMutation({
args: {
syncRunId: v.id('syncRuns'),
threadId: v.optional(v.id('threads')),
status: v.optional(syncStatus),
upstreamFrom: v.optional(v.string()),
upstreamTo: v.optional(v.string()),
summary: v.optional(v.string()),
aiAssessment: v.optional(v.string()),
mergeRequestUrl: v.optional(v.string()),
error: v.optional(v.string()),
decision: v.optional(syncDecision),
},
handler: async (ctx, args) => {
const patch: Partial<Doc<'syncRuns'>> = { updatedAt: Date.now() };
if (args.threadId !== undefined) patch.threadId = args.threadId;
if (args.status !== undefined) patch.status = args.status;
if (args.upstreamFrom !== undefined) patch.upstreamFrom = args.upstreamFrom;
if (args.upstreamTo !== undefined) patch.upstreamTo = args.upstreamTo;
if (args.summary !== undefined) patch.summary = args.summary;
if (args.aiAssessment !== undefined) {
patch.aiAssessment = args.aiAssessment;
}
if (args.mergeRequestUrl !== undefined) {
patch.mergeRequestUrl = args.mergeRequestUrl;
}
if (args.error !== undefined) patch.error = args.error;
if (args.decision !== undefined) patch.decision = args.decision;
await ctx.db.patch(args.syncRunId, patch);
return { success: true };
},
});