Update Convex with no payload to be just like convex with payload but without payload

This commit is contained in:
Gabriel Brown
2026-06-21 15:35:42 -05:00
parent 13b8b36c4c
commit fba73a92ce
130 changed files with 15637 additions and 32018 deletions
File diff suppressed because one or more lines are too long
-2
View File
@@ -15,7 +15,6 @@ import type * as custom_auth_providers_password from "../custom/auth/providers/p
import type * as custom_auth_providers_usesend from "../custom/auth/providers/usesend.js";
import type * as files from "../files.js";
import type * as http from "../http.js";
import type * as utils from "../utils.js";
import type {
ApiFromModules,
@@ -31,7 +30,6 @@ declare const fullApi: ApiFromModules<{
"custom/auth/providers/usesend": typeof custom_auth_providers_usesend;
files: typeof files;
http: typeof http;
utils: typeof utils;
}>;
/**
+2 -3
View File
@@ -8,7 +8,7 @@ import {
import { ConvexError, v } from 'convex/values';
import type { Doc, Id } from './_generated/dataModel';
import type { MutationCtx, QueryCtx } from './_generated/server';
import type { QueryCtx } from './_generated/server';
import { api } from './_generated/api';
import { action, mutation, query } from './_generated/server';
import { Password, validatePassword } from './custom/auth';
@@ -96,11 +96,10 @@ export const updateUserPassword = action({
if (!userId) throw new ConvexError('Not authenticated.');
const user = await ctx.runQuery(api.auth.getUser, { userId });
if (!user?.email) throw new ConvexError('User not found.');
const verified = await retrieveAccount(ctx, {
await retrieveAccount(ctx, {
provider: 'password',
account: { id: user.email, secret: currentPassword },
});
if (!verified) throw new ConvexError('Current password is incorrect.');
if (!validatePassword(newPassword))
throw new ConvexError('Invalid password.');
@@ -11,32 +11,35 @@ export default function UseSendProvider(config: EmailUserConfig): EmailConfig {
from: process.env.USESEND_FROM_EMAIL ?? 'noreply@example.com',
maxAge: 24 * 60 * 60, // 24 hours
async generateVerificationToken() {
generateVerificationToken: () => {
const random: RandomReader = {
read(bytes) {
crypto.getRandomValues(bytes);
read: (bytes) => {
crypto.getRandomValues(bytes as Uint8Array<ArrayBuffer>);
},
};
return generateRandomString(random, alphabet('0-9'), 6);
},
async sendVerificationRequest(params) {
sendVerificationRequest: async (params) => {
const { identifier: to, provider, url, token } = params;
// Derive a display name from the site URL, fallback to 'App'
const siteUrl = process.env.USESEND_FROM_EMAIL ?? '';
const appName = siteUrl.split('@')[1]?.split('.')[0] ?? 'App';
const useSend = new UseSend(
process.env.USESEND_API_KEY!,
process.env.USESEND_URL!,
);
const apiKey = process.env.USESEND_API_KEY;
const useSendUrl = process.env.USESEND_URL;
if (!apiKey || !useSendUrl) {
throw new Error('USESEND_API_KEY and USESEND_URL must be set.');
}
const useSend = new UseSend(apiKey, useSendUrl);
// For password reset, we want to send the code, not the magic link
const isPasswordReset =
url.includes('reset') || provider.id?.includes('reset');
url.includes('reset') || provider.id.includes('reset');
const result = await useSend.emails.send({
from: provider.from!,
from: provider.from ?? 'noreply@example.com',
to: [to],
subject: isPasswordReset
? `Reset your password - ${appName}`
+10
View File
@@ -0,0 +1,10 @@
// Declare process.env for Convex backend environment variables.
// Convex supports process.env to read variables set in the Convex Dashboard.
declare const process: {
readonly env: {
readonly USESEND_API_KEY?: string;
readonly USESEND_URL?: string;
readonly USESEND_FROM_EMAIL?: string;
readonly [key: string]: string | undefined;
};
};
+1
View File
@@ -18,6 +18,7 @@ const applicationTables = {
phoneVerificationTime: v.optional(v.number()),
isAnonymous: v.optional(v.boolean()),
/* Fields below here are custom & not defined in authTables */
isAdmin: v.optional(v.boolean()),
themePreference: v.optional(
v.union(v.literal('light'), v.literal('dark'), v.literal('system')),
),
-16
View File
@@ -1,16 +0,0 @@
export function missingEnvVariableUrl(envVarName: string, whereToGet: string) {
const deployment = deploymentName();
if (!deployment) return `Missing ${envVarName} in environment variables.`;
return (
`\n Missing ${envVarName} in environment variables.\n\n` +
` Get it from ${whereToGet} .\n Paste it on the Convex dashboard:\n` +
` https://dashboard.convex.dev/d/${deployment}/settings?var=${envVarName}`
);
}
export function deploymentName() {
const url = process.env.CONVEX_CLOUD_URL;
if (!url) return undefined;
const regex = new RegExp('https://(.+).convex.cloud');
return regex.exec(url)?.[1];
}
+10
View File
@@ -0,0 +1,10 @@
import { defineConfig } from 'eslint/config';
import { baseConfig } from '@gib/eslint-config/base';
export default defineConfig(
{
ignores: ['convex/_generated/**', 'types/**', 'scripts/**', 'dist/**'],
},
baseConfig,
);
+15 -6
View File
@@ -14,31 +14,40 @@
"scripts": {
"dev": "bun with-env convex dev",
"dev:tunnel": "bun with-env convex dev",
"dev:web": "bun with-env convex dev",
"setup": "bun with-env convex dev --until-success",
"clean": "git clean -xdf .cache .turbo dist node_modules",
"format": "prettier --check . --ignore-path ../../.gitignore",
"lint": "eslint --flag unstable_native_nodejs_ts_config",
"typecheck": "tsc --noEmit",
"with-env": "dotenv -e ../../.env --"
"test:unit": "vitest run --project unit",
"test:integration": "vitest run --project integration --passWithNoTests",
"test:component": "vitest run --project component --passWithNoTests",
"with-env": "sh ../../scripts/with-env ${INFISICAL_ENV:-dev} --"
},
"dependencies": {
"@oslojs/crypto": "^1.0.1",
"@react-email/components": "0.5.4",
"@react-email/render": "^1.4.0",
"@react-email/components": "1.0.10",
"@react-email/render": "^2.0.4",
"convex": "catalog:convex",
"react": "catalog:react19",
"react-dom": "catalog:react19",
"usesend-js": "^1.5.6",
"usesend-js": "^1.6.3",
"zod": "catalog:"
},
"devDependencies": {
"@edge-runtime/vm": "catalog:test",
"@gib/eslint-config": "workspace:*",
"@gib/prettier-config": "workspace:*",
"@gib/tsconfig": "workspace:*",
"@gib/vitest-config": "workspace:*",
"@types/node": "catalog:",
"convex-test": "catalog:test",
"eslint": "catalog:",
"prettier": "catalog:",
"react-email": "4.2.11",
"typescript": "catalog:"
"react-email": "5.2.10",
"typescript": "catalog:",
"vitest": "catalog:test"
},
"prettier": "@gib/prettier-config"
}
@@ -0,0 +1,13 @@
import { convexTest } from 'convex-test';
import { describe, expect, test } from 'vitest';
import schema from '../../convex/schema';
const modules = import.meta.glob('../../convex/**/*.*s');
describe('convex-test harness', () => {
test('boots and executes against the project schema', async () => {
const t = convexTest(schema, modules);
expect(await t.run(() => Promise.resolve(42))).toBe(42);
});
});
+1
View File
@@ -0,0 +1 @@
/// <reference types="vite/client" />
+6
View File
@@ -0,0 +1,6 @@
{
"extends": "@gib/tsconfig/base.json",
"compilerOptions": { "lib": ["ES2022", "DOM"], "types": ["node"] },
"include": ["tests", "vitest.config.ts"],
"exclude": ["node_modules", "convex/_generated"]
}
+13
View File
@@ -0,0 +1,13 @@
import { defineConfig } from 'vitest/config';
import { convexProject, nodeProject } from '@gib/vitest-config';
export default defineConfig({
test: {
projects: [
convexProject('unit', ['tests/unit/**/*.test.ts']),
convexProject('integration', ['tests/integration/**/*.test.ts']),
nodeProject('component', ['tests/component/**/*.test.{ts,tsx}']),
],
},
});