Adds nullable user_profiles.signing_email — the address representing a user in SIGNING contexts only (EOI developer/approver signer slot, in-CRM 'your turn to sign' notification, 'awaiting my signature' hub tab). Never a login credential; better-auth identity stays single-email. NULL → user signs as their own login email. Lets a person who logs in as e.g. abbie@ sign on behalf of a shared role mailbox (sales@) without changing their login. Additive + backward-compatible: every existing user (NULL override) is byte-for-byte unaffected. - migration 0100 + drizzle column (nullable, no unique constraint) - resolveCrmUser: signing_email ?? user.email - listDocuments hub filter: match the caller's owned email SET (login + override) for awaiting_me / awaiting_them (currentUserEmail -> currentUserEmails) - ctx.user.signingEmail surfaced from the already-loaded profile (no extra query) - updateUser persists/lowercases the override; '' clears it (edit-only) - admin Edit-User form: optional 'Signing email' field - TDD: validator unit test + 3 integration tests (resolver override+fallback, updateUser persist/clear, hub awaiting_me matches override) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L2qc3xZTfif7N4Wq3QDa8X
43 lines
1.3 KiB
TypeScript
43 lines
1.3 KiB
TypeScript
/**
|
|
* Helper to invoke route inner-handlers directly, bypassing withAuth.
|
|
* Route files must export the inner handler (in addition to the withAuth-wrapped HTTP method).
|
|
*/
|
|
import { NextRequest } from 'next/server';
|
|
import type { AuthContext } from '@/lib/api/helpers';
|
|
import type { RolePermissions } from '@/lib/db/schema/users';
|
|
|
|
export interface MockCtxOptions {
|
|
portId: string;
|
|
isSuperAdmin?: boolean;
|
|
permissions?: RolePermissions | null;
|
|
userId?: string;
|
|
}
|
|
|
|
export function makeMockCtx(opts: MockCtxOptions): AuthContext {
|
|
return {
|
|
userId: opts.userId ?? 'test-user',
|
|
portId: opts.portId,
|
|
portSlug: 'test-port',
|
|
isSuperAdmin: opts.isSuperAdmin ?? false,
|
|
permissions: opts.permissions ?? null,
|
|
user: { email: 'test@example.com', name: 'Test User', signingEmail: null },
|
|
ipAddress: '127.0.0.1',
|
|
userAgent: 'vitest/1.0',
|
|
};
|
|
}
|
|
|
|
export function makeMockRequest(
|
|
method: string,
|
|
url: string,
|
|
opts: { body?: unknown; headers?: Record<string, string> } = {},
|
|
): NextRequest {
|
|
const init: { method: string; headers: Record<string, string>; body?: string } = {
|
|
method,
|
|
headers: { 'content-type': 'application/json', ...(opts.headers ?? {}) },
|
|
};
|
|
if (opts.body !== undefined) {
|
|
init.body = JSON.stringify(opts.body);
|
|
}
|
|
return new NextRequest(url, init);
|
|
}
|