Files
spoon/packages/backend/convex/spoonSettings.ts
T
Gabriel Brown 2dfa97ee4f
Build and Push Next App / quality (push) Failing after 48s
Build and Push Next App / build-next (push) Has been skipped
Add agent workflows & stuff
2026-06-21 21:15:15 -05:00

155 lines
4.8 KiB
TypeScript

import { v } from 'convex/values';
import type { Doc } from './_generated/dataModel';
import {
internalMutation,
internalQuery,
mutation,
query,
} from './_generated/server';
import { getOwnedSpoon, getRequiredUserId } from './model';
const defaultSettings = {
autoRefreshEnabled: true,
autoReviewEnabled: true,
autoSyncEnabled: false,
requireAiLowRiskForSync: true,
requireCleanCompareForSync: true,
ignoredFilePatterns: [] as string[],
importantFilePatterns: [] as string[],
};
export const getForSpoon = query({
args: { spoonId: v.id('spoons') },
handler: async (ctx, { spoonId }) => {
const ownerId = await getRequiredUserId(ctx);
await getOwnedSpoon(ctx, spoonId, ownerId);
return (
(await ctx.db
.query('spoonSettings')
.withIndex('by_spoon', (q) => q.eq('spoonId', spoonId))
.first()) ?? { spoonId, ownerId, ...defaultSettings }
);
},
});
export const ensureForSpoon = internalMutation({
args: { spoonId: v.id('spoons'), ownerId: v.id('users') },
handler: async (ctx, { spoonId, ownerId }) => {
const existing = await ctx.db
.query('spoonSettings')
.withIndex('by_spoon', (q) => q.eq('spoonId', spoonId))
.first();
if (existing) return existing._id;
const now = Date.now();
return await ctx.db.insert('spoonSettings', {
spoonId,
ownerId,
...defaultSettings,
createdAt: now,
updatedAt: now,
});
},
});
export const getInternal = internalQuery({
args: { spoonId: v.id('spoons'), ownerId: v.id('users') },
handler: async (ctx, { spoonId, ownerId }) => {
const settings = await ctx.db
.query('spoonSettings')
.withIndex('by_spoon', (q) => q.eq('spoonId', spoonId))
.first();
if (settings && settings.ownerId !== ownerId) {
throw new Error('Spoon settings ownership mismatch.');
}
return settings;
},
});
export const listRefreshDue = internalQuery({
args: { limit: v.number() },
handler: async (ctx, { limit }) => {
const now = Date.now();
const settings = await ctx.db.query('spoonSettings').collect();
const due = [];
for (const item of settings) {
if (!item.autoRefreshEnabled) continue;
const spoon = await ctx.db.get(item.spoonId);
if (
!spoon ||
spoon.status === 'archived' ||
spoon.provider !== 'github'
) {
continue;
}
if (spoon.syncCadence === 'manual') continue;
const cadenceMs =
spoon.syncCadence === 'weekly'
? 7 * 24 * 60 * 60 * 1000
: 24 * 60 * 60 * 1000;
const last = spoon.lastGithubRefreshAt ?? spoon.lastCheckedAt ?? 0;
if (now - last >= cadenceMs) {
due.push({ spoonId: item.spoonId, ownerId: item.ownerId });
}
if (due.length >= limit) break;
}
return due;
},
});
export const update = mutation({
args: {
spoonId: v.id('spoons'),
autoRefreshEnabled: v.optional(v.boolean()),
autoReviewEnabled: v.optional(v.boolean()),
autoSyncEnabled: v.optional(v.boolean()),
requireAiLowRiskForSync: v.optional(v.boolean()),
requireCleanCompareForSync: v.optional(v.boolean()),
ignoredFilePatterns: v.optional(v.array(v.string())),
importantFilePatterns: v.optional(v.array(v.string())),
},
handler: async (ctx, args) => {
const ownerId = await getRequiredUserId(ctx);
await getOwnedSpoon(ctx, args.spoonId, ownerId);
let settings = await ctx.db
.query('spoonSettings')
.withIndex('by_spoon', (q) => q.eq('spoonId', args.spoonId))
.first();
if (!settings) {
const id = await ctx.db.insert('spoonSettings', {
spoonId: args.spoonId,
ownerId,
...defaultSettings,
createdAt: Date.now(),
updatedAt: Date.now(),
});
settings = await ctx.db.get(id);
}
const patch: Partial<Doc<'spoonSettings'>> = { updatedAt: Date.now() };
if (args.autoRefreshEnabled !== undefined) {
patch.autoRefreshEnabled = args.autoRefreshEnabled;
}
if (args.autoReviewEnabled !== undefined) {
patch.autoReviewEnabled = args.autoReviewEnabled;
}
if (args.autoSyncEnabled !== undefined) {
patch.autoSyncEnabled = args.autoSyncEnabled;
}
if (args.requireAiLowRiskForSync !== undefined) {
patch.requireAiLowRiskForSync = args.requireAiLowRiskForSync;
}
if (args.requireCleanCompareForSync !== undefined) {
patch.requireCleanCompareForSync = args.requireCleanCompareForSync;
}
if (args.ignoredFilePatterns !== undefined) {
patch.ignoredFilePatterns = args.ignoredFilePatterns;
}
if (args.importantFilePatterns !== undefined) {
patch.importantFilePatterns = args.importantFilePatterns;
}
if (!settings) throw new Error('Spoon settings not found.');
await ctx.db.patch(settings._id, patch);
return { success: true };
},
});