diff --git a/teressa-copeland-homes/drizzle/0009_luxuriant_catseye.sql b/teressa-copeland-homes/drizzle/0009_luxuriant_catseye.sql new file mode 100644 index 0000000..7bd85f4 --- /dev/null +++ b/teressa-copeland-homes/drizzle/0009_luxuriant_catseye.sql @@ -0,0 +1 @@ +ALTER TABLE "users" ADD COLUMN "agent_initials_data" text; \ No newline at end of file diff --git a/teressa-copeland-homes/drizzle/meta/0009_snapshot.json b/teressa-copeland-homes/drizzle/meta/0009_snapshot.json new file mode 100644 index 0000000..c9628ed --- /dev/null +++ b/teressa-copeland-homes/drizzle/meta/0009_snapshot.json @@ -0,0 +1,472 @@ +{ + "id": "ed7dbd15-557e-465b-8e8e-e1398a706cd4", + "prevId": "ac3ad071-49ad-4dfc-8c73-91e23e3b156f", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.audit_events": { + "name": "audit_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "audit_event_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "audit_events_document_id_documents_id_fk": { + "name": "audit_events_document_id_documents_id_fk", + "tableFrom": "audit_events", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.clients": { + "name": "clients", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "property_address": { + "name": "property_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.documents": { + "name": "documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "document_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'Draft'" + }, + "sent_at": { + "name": "sent_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "form_template_id": { + "name": "form_template_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "file_path": { + "name": "file_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "signature_fields": { + "name": "signature_fields", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "text_fill_data": { + "name": "text_fill_data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "assigned_client_id": { + "name": "assigned_client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prepared_file_path": { + "name": "prepared_file_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_addresses": { + "name": "email_addresses", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "signed_file_path": { + "name": "signed_file_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pdf_hash": { + "name": "pdf_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "signed_at": { + "name": "signed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "documents_client_id_clients_id_fk": { + "name": "documents_client_id_clients_id_fk", + "tableFrom": "documents", + "tableTo": "clients", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "documents_form_template_id_form_templates_id_fk": { + "name": "documents_form_template_id_form_templates_id_fk", + "tableFrom": "documents", + "tableTo": "form_templates", + "columnsFrom": [ + "form_template_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.form_templates": { + "name": "form_templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "filename": { + "name": "filename", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "form_templates_filename_unique": { + "name": "form_templates_filename_unique", + "nullsNotDistinct": false, + "columns": [ + "filename" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.signing_tokens": { + "name": "signing_tokens", + "schema": "", + "columns": { + "jti": { + "name": "jti", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "used_at": { + "name": "used_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "signing_tokens_document_id_documents_id_fk": { + "name": "signing_tokens_document_id_documents_id_fk", + "tableFrom": "signing_tokens", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "agent_signature_data": { + "name": "agent_signature_data", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_initials_data": { + "name": "agent_initials_data", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.audit_event_type": { + "name": "audit_event_type", + "schema": "public", + "values": [ + "document_prepared", + "email_sent", + "link_opened", + "document_viewed", + "signature_submitted", + "pdf_hash_computed" + ] + }, + "public.document_status": { + "name": "document_status", + "schema": "public", + "values": [ + "Draft", + "Sent", + "Viewed", + "Signed" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/teressa-copeland-homes/drizzle/meta/_journal.json b/teressa-copeland-homes/drizzle/meta/_journal.json index 6afd526..809fb7e 100644 --- a/teressa-copeland-homes/drizzle/meta/_journal.json +++ b/teressa-copeland-homes/drizzle/meta/_journal.json @@ -64,6 +64,13 @@ "when": 1774123280818, "tag": "0008_windy_cloak", "breakpoints": true + }, + { + "idx": 9, + "version": "7", + "when": 1774126681669, + "tag": "0009_luxuriant_catseye", + "breakpoints": true } ] } \ No newline at end of file diff --git a/teressa-copeland-homes/src/app/api/agent/initials/route.ts b/teressa-copeland-homes/src/app/api/agent/initials/route.ts new file mode 100644 index 0000000..0cb8ada --- /dev/null +++ b/teressa-copeland-homes/src/app/api/agent/initials/route.ts @@ -0,0 +1,36 @@ +import { auth } from '@/lib/auth'; +import { db } from '@/lib/db'; +import { users } from '@/lib/db/schema'; +import { eq } from 'drizzle-orm'; + +export async function GET() { + const session = await auth(); + if (!session?.user?.id) return new Response('Unauthorized', { status: 401 }); + + const user = await db.query.users.findFirst({ + where: eq(users.id, session.user.id), + columns: { agentInitialsData: true }, + }); + + return Response.json({ agentInitialsData: user?.agentInitialsData ?? null }); +} + +export async function PUT(req: Request) { + const session = await auth(); + if (!session?.user?.id) return new Response('Unauthorized', { status: 401 }); + + const { dataURL } = await req.json() as { dataURL: string }; + + if (!dataURL || !dataURL.startsWith('data:image/png;base64,')) { + return Response.json({ error: 'Invalid initials data' }, { status: 422 }); + } + if (dataURL.length > 50_000) { + return Response.json({ error: 'Initials data too large' }, { status: 422 }); + } + + await db.update(users) + .set({ agentInitialsData: dataURL }) + .where(eq(users.id, session.user.id)); + + return Response.json({ ok: true }); +} diff --git a/teressa-copeland-homes/src/lib/db/schema.ts b/teressa-copeland-homes/src/lib/db/schema.ts index 9c679ef..c609994 100644 --- a/teressa-copeland-homes/src/lib/db/schema.ts +++ b/teressa-copeland-homes/src/lib/db/schema.ts @@ -7,7 +7,8 @@ export type SignatureFieldType = | 'text' | 'checkbox' | 'date' - | 'agent-signature'; + | 'agent-signature' + | 'agent-initials'; export interface SignatureFieldData { id: string; @@ -34,7 +35,8 @@ export function getFieldType(field: SignatureFieldData): SignatureFieldType { * surface to the client as required unsigned fields. */ export function isClientVisibleField(field: SignatureFieldData): boolean { - return getFieldType(field) !== 'agent-signature'; + const t = getFieldType(field); + return t !== 'agent-signature' && t !== 'agent-initials'; } export const users = pgTable("users", { @@ -43,6 +45,7 @@ export const users = pgTable("users", { passwordHash: text("password_hash").notNull(), createdAt: timestamp("created_at").defaultNow().notNull(), agentSignatureData: text("agent_signature_data"), + agentInitialsData: text("agent_initials_data"), }); export const documentStatusEnum = pgEnum("document_status", [