Add agent workflows & stuff
Build and Push Next App / quality (push) Failing after 48s
Build and Push Next App / build-next (push) Has been skipped

This commit is contained in:
Gabriel Brown
2026-06-21 21:15:15 -05:00
parent cf7ff2ee4e
commit 2dfa97ee4f
102 changed files with 8488 additions and 161 deletions
@@ -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 };
},
});