import { ConvexError, v } from 'convex/values'; import type { Doc } from './_generated/dataModel'; import { mutation, query } from './_generated/server'; import { getOwnedSpoon, getRequiredUserId, optionalText, requireText, } from './model'; const provider = v.union( v.literal('github'), v.literal('gitea'), v.literal('gitlab'), v.literal('other'), ); const visibility = v.union( v.literal('public'), v.literal('private'), v.literal('internal'), v.literal('unknown'), ); const maintenanceMode = v.union( v.literal('watch'), v.literal('auto_pr'), v.literal('paused'), ); const syncCadence = v.union( v.literal('daily'), v.literal('weekly'), v.literal('manual'), ); const productionRefStrategy = v.union( v.literal('default_branch'), v.literal('latest_release'), v.literal('tag_pattern'), ); const spoonStatus = v.union( v.literal('draft'), v.literal('active'), v.literal('needs_connection'), v.literal('paused'), v.literal('archived'), ); const hasForkMetadata = (args: { forkOwner?: string; forkRepo?: string; forkUrl?: string; }) => Boolean( args.forkOwner?.trim() && args.forkRepo?.trim() && args.forkUrl?.trim(), ); export const listMine = query({ args: {}, handler: async (ctx) => { const ownerId = await getRequiredUserId(ctx); const spoons = await ctx.db .query('spoons') .withIndex('by_owner', (q) => q.eq('ownerId', ownerId)) .order('desc') .collect(); return spoons.filter((spoon) => spoon.status !== 'archived'); }, }); export const get = query({ args: { spoonId: v.id('spoons') }, handler: async (ctx, { spoonId }) => { const ownerId = await getRequiredUserId(ctx); return getOwnedSpoon(ctx, spoonId, ownerId); }, }); export const createManual = mutation({ args: { name: v.string(), description: v.optional(v.string()), provider, upstreamOwner: v.string(), upstreamRepo: v.string(), upstreamDefaultBranch: v.string(), upstreamUrl: v.string(), forkOwner: v.optional(v.string()), forkRepo: v.optional(v.string()), forkDefaultBranch: v.optional(v.string()), forkUrl: v.optional(v.string()), visibility, maintenanceMode, syncCadence, productionRefStrategy, tagPattern: v.optional(v.string()), }, handler: async (ctx, args) => { const ownerId = await getRequiredUserId(ctx); const now = Date.now(); const forkOwner = optionalText(args.forkOwner); const forkRepo = optionalText(args.forkRepo); const forkUrl = optionalText(args.forkUrl); const status = hasForkMetadata({ forkOwner, forkRepo, forkUrl }) ? 'draft' : 'needs_connection'; return await ctx.db.insert('spoons', { ownerId, name: requireText(args.name, 'Spoon name'), description: optionalText(args.description), provider: args.provider, upstreamOwner: requireText(args.upstreamOwner, 'Upstream owner'), upstreamRepo: requireText(args.upstreamRepo, 'Upstream repository'), upstreamDefaultBranch: requireText( args.upstreamDefaultBranch, 'Upstream default branch', ), upstreamUrl: requireText(args.upstreamUrl, 'Upstream URL'), forkOwner, forkRepo, forkDefaultBranch: optionalText(args.forkDefaultBranch), forkUrl, visibility: args.visibility, maintenanceMode: args.maintenanceMode, syncCadence: args.syncCadence, productionRefStrategy: args.productionRefStrategy, tagPattern: optionalText(args.tagPattern), status, createdAt: now, updatedAt: now, }); }, }); export const updateSettings = mutation({ args: { spoonId: v.id('spoons'), maintenanceMode: v.optional(maintenanceMode), syncCadence: v.optional(syncCadence), productionRefStrategy: v.optional(productionRefStrategy), tagPattern: v.optional(v.string()), status: v.optional(spoonStatus), }, handler: async (ctx, args) => { const ownerId = await getRequiredUserId(ctx); await getOwnedSpoon(ctx, args.spoonId, ownerId); const patch: Partial> = { updatedAt: Date.now() }; if (args.maintenanceMode) patch.maintenanceMode = args.maintenanceMode; if (args.syncCadence) patch.syncCadence = args.syncCadence; if (args.productionRefStrategy) { patch.productionRefStrategy = args.productionRefStrategy; } if (args.tagPattern !== undefined) patch.tagPattern = optionalText(args.tagPattern); if (args.status) patch.status = args.status; await ctx.db.patch(args.spoonId, patch); return { success: true }; }, }); export const archive = mutation({ args: { spoonId: v.id('spoons') }, handler: async (ctx, { spoonId }) => { const ownerId = await getRequiredUserId(ctx); const spoon = await getOwnedSpoon(ctx, spoonId, ownerId); if (spoon.status === 'archived') throw new ConvexError('Spoon is archived.'); await ctx.db.patch(spoonId, { status: 'archived', updatedAt: Date.now(), }); return { success: true }; }, });