Add agent workflows & stuff
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
'use node';
|
||||
|
||||
import { getAuthUserId } from '@convex-dev/auth/server';
|
||||
import { ConvexError, v } from 'convex/values';
|
||||
|
||||
import type { Id } from './_generated/dataModel';
|
||||
import type { ActionCtx } from './_generated/server';
|
||||
import { internal } from './_generated/api';
|
||||
import { action } from './_generated/server';
|
||||
import { encryptSecret } from './secretCrypto';
|
||||
|
||||
const secretNamePattern = /^[A-Z_][A-Z0-9_]*$/;
|
||||
|
||||
const getRequiredUserId = async (ctx: ActionCtx): Promise<Id<'users'>> => {
|
||||
const userId = await getAuthUserId(ctx);
|
||||
if (!userId) throw new ConvexError('Not authenticated.');
|
||||
return userId;
|
||||
};
|
||||
|
||||
const previewSecret = (value: string) => {
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed) return 'empty';
|
||||
if (trimmed.length <= 8) return 'configured';
|
||||
return `${trimmed.slice(0, 3)}...${trimmed.slice(-3)}`;
|
||||
};
|
||||
|
||||
const normalizeName = (name: string) => {
|
||||
const normalized = name.trim().toUpperCase();
|
||||
if (!secretNamePattern.test(normalized)) {
|
||||
throw new ConvexError(
|
||||
'Secret names must look like environment variables, for example AUTH_SECRET.',
|
||||
);
|
||||
}
|
||||
return normalized;
|
||||
};
|
||||
|
||||
const optionalText = (value?: string) => {
|
||||
const trimmed = value?.trim();
|
||||
if (!trimmed) return undefined;
|
||||
return trimmed;
|
||||
};
|
||||
|
||||
export const create = action({
|
||||
args: {
|
||||
spoonId: v.id('spoons'),
|
||||
name: v.string(),
|
||||
value: v.string(),
|
||||
description: v.optional(v.string()),
|
||||
},
|
||||
handler: async (ctx, args): Promise<Id<'spoonSecrets'>> => {
|
||||
const ownerId = await getRequiredUserId(ctx);
|
||||
await ctx.runQuery(internal.spoons.getOwnedForAction, {
|
||||
spoonId: args.spoonId,
|
||||
ownerId,
|
||||
});
|
||||
const value = args.value.trim();
|
||||
if (!value) throw new ConvexError('Secret value is required.');
|
||||
return await ctx.runMutation(
|
||||
internal.spoonSecrets.upsertEncryptedInternal,
|
||||
{
|
||||
spoonId: args.spoonId,
|
||||
ownerId,
|
||||
name: normalizeName(args.name),
|
||||
encryptedValue: encryptSecret(value),
|
||||
valuePreview: previewSecret(value),
|
||||
description: optionalText(args.description),
|
||||
},
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export const update = action({
|
||||
args: {
|
||||
secretId: v.id('spoonSecrets'),
|
||||
value: v.optional(v.string()),
|
||||
description: v.optional(v.string()),
|
||||
},
|
||||
handler: async (ctx, args): Promise<{ success: true }> => {
|
||||
const ownerId = await getRequiredUserId(ctx);
|
||||
const patch = args.value?.trim()
|
||||
? {
|
||||
encryptedValue: encryptSecret(args.value.trim()),
|
||||
valuePreview: previewSecret(args.value.trim()),
|
||||
}
|
||||
: {};
|
||||
await ctx.runMutation(internal.spoonSecrets.patchEncryptedInternal, {
|
||||
secretId: args.secretId,
|
||||
ownerId,
|
||||
description: optionalText(args.description),
|
||||
...patch,
|
||||
});
|
||||
return { success: true };
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user