feat(14-01): add multi-signer types and columns to schema.ts
- Add signerEmail?: string to SignatureFieldData interface
- Add getSignerEmail() helper function with fallback pattern
- Add DocumentSigner interface { email, color }
- Add documents.signers JSONB column typed as DocumentSigner[]
- Add documents.completionTriggeredAt nullable TIMESTAMP column
- Add signingTokens.signerEmail nullable TEXT column
This commit is contained in:
@@ -18,6 +18,7 @@ export interface SignatureFieldData {
|
|||||||
width: number; // PDF points (default: 144 — 2 inches)
|
width: number; // PDF points (default: 144 — 2 inches)
|
||||||
height: number; // PDF points (default: 36 — 0.5 inches)
|
height: number; // PDF points (default: 36 — 0.5 inches)
|
||||||
type?: SignatureFieldType; // Optional — v1.0 documents have no type; fallback = 'client-signature'
|
type?: SignatureFieldType; // Optional — v1.0 documents have no type; fallback = 'client-signature'
|
||||||
|
signerEmail?: string; // Optional — absent = legacy single-signer or agent-owned field
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -39,6 +40,16 @@ export function isClientVisibleField(field: SignatureFieldData): boolean {
|
|||||||
return t !== 'agent-signature' && t !== 'agent-initials';
|
return t !== 'agent-signature' && t !== 'agent-initials';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Safe signer email reader — returns the field's signerEmail or a fallback.
|
||||||
|
* Legacy single-signer documents have no signerEmail on their fields;
|
||||||
|
* this coalesces to the fallback (typically the document's single recipient email).
|
||||||
|
* ALWAYS use this instead of reading field.signerEmail directly.
|
||||||
|
*/
|
||||||
|
export function getSignerEmail(field: SignatureFieldData, fallbackEmail: string): string {
|
||||||
|
return field.signerEmail ?? fallbackEmail;
|
||||||
|
}
|
||||||
|
|
||||||
export const users = pgTable("users", {
|
export const users = pgTable("users", {
|
||||||
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
||||||
email: text("email").notNull().unique(),
|
email: text("email").notNull().unique(),
|
||||||
@@ -72,6 +83,12 @@ export const formTemplates = pgTable("form_templates", {
|
|||||||
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
updatedAt: timestamp("updated_at").defaultNow().notNull(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/** Shape of each entry in documents.signers JSONB array. */
|
||||||
|
export interface DocumentSigner {
|
||||||
|
email: string;
|
||||||
|
color: string;
|
||||||
|
}
|
||||||
|
|
||||||
export const documents = pgTable("documents", {
|
export const documents = pgTable("documents", {
|
||||||
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
||||||
name: text("name").notNull(),
|
name: text("name").notNull(),
|
||||||
@@ -92,6 +109,10 @@ export const documents = pgTable("documents", {
|
|||||||
signedFilePath: text("signed_file_path"),
|
signedFilePath: text("signed_file_path"),
|
||||||
pdfHash: text("pdf_hash"),
|
pdfHash: text("pdf_hash"),
|
||||||
signedAt: timestamp("signed_at"),
|
signedAt: timestamp("signed_at"),
|
||||||
|
/** Per-signer list with assigned colors. NULL = legacy single-signer document. */
|
||||||
|
signers: jsonb("signers").$type<DocumentSigner[]>(),
|
||||||
|
/** Atomic completion guard — set once by the last signer's handler. NULL = not yet completed. */
|
||||||
|
completionTriggeredAt: timestamp("completion_triggered_at"),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const documentsRelations = relations(documents, ({ one }) => ({
|
export const documentsRelations = relations(documents, ({ one }) => ({
|
||||||
@@ -115,6 +136,8 @@ export const signingTokens = pgTable('signing_tokens', {
|
|||||||
jti: text('jti').primaryKey(),
|
jti: text('jti').primaryKey(),
|
||||||
documentId: text('document_id').notNull()
|
documentId: text('document_id').notNull()
|
||||||
.references(() => documents.id, { onDelete: 'cascade' }),
|
.references(() => documents.id, { onDelete: 'cascade' }),
|
||||||
|
/** Signer this token belongs to. NULL = legacy single-signer token. */
|
||||||
|
signerEmail: text('signer_email'),
|
||||||
createdAt: timestamp('created_at').defaultNow().notNull(),
|
createdAt: timestamp('created_at').defaultNow().notNull(),
|
||||||
expiresAt: timestamp('expires_at').notNull(),
|
expiresAt: timestamp('expires_at').notNull(),
|
||||||
usedAt: timestamp('used_at'),
|
usedAt: timestamp('used_at'),
|
||||||
|
|||||||
Reference in New Issue
Block a user