From baa1c785a5a98667ed338c6966caf6bcc2ac2b2e Mon Sep 17 00:00:00 2001 From: Chandler Copeland Date: Sat, 21 Mar 2026 12:13:55 -0600 Subject: [PATCH] =?UTF-8?q?feat(09-01):=20add=20property=5Faddress=20colum?= =?UTF-8?q?n=20to=20clients=20=E2=80=94=20schema,=20migration,=20server=20?= =?UTF-8?q?actions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add propertyAddress: text("property_address") nullable column to clients pgTable - Generate migration drizzle/0007_equal_nekra.sql: ALTER TABLE "clients" ADD COLUMN "property_address" text - Apply migration successfully to local postgres database - Extend clientSchema Zod schema with propertyAddress: z.string().optional() - createClient: persist propertyAddress || null to coerce empty string to NULL - updateClient: persist propertyAddress || null alongside name, email, updatedAt --- .../drizzle/0007_equal_nekra.sql | 1 + .../drizzle/meta/0007_snapshot.json | 460 ++++++++++++++++++ .../drizzle/meta/_journal.json | 7 + .../src/lib/actions/clients.ts | 6 +- teressa-copeland-homes/src/lib/db/schema.ts | 1 + 5 files changed, 474 insertions(+), 1 deletion(-) create mode 100644 teressa-copeland-homes/drizzle/0007_equal_nekra.sql create mode 100644 teressa-copeland-homes/drizzle/meta/0007_snapshot.json diff --git a/teressa-copeland-homes/drizzle/0007_equal_nekra.sql b/teressa-copeland-homes/drizzle/0007_equal_nekra.sql new file mode 100644 index 0000000..b7b7c1a --- /dev/null +++ b/teressa-copeland-homes/drizzle/0007_equal_nekra.sql @@ -0,0 +1 @@ +ALTER TABLE "clients" ADD COLUMN "property_address" text; \ No newline at end of file diff --git a/teressa-copeland-homes/drizzle/meta/0007_snapshot.json b/teressa-copeland-homes/drizzle/meta/0007_snapshot.json new file mode 100644 index 0000000..9cb2ece --- /dev/null +++ b/teressa-copeland-homes/drizzle/meta/0007_snapshot.json @@ -0,0 +1,460 @@ +{ + "id": "5883bfc1-4ee1-441f-a389-9f5b930803ce", + "prevId": "a3f8c2e1-5b4d-4f7a-9c6e-d2b1e8f0a3c5", + "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()" + } + }, + "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 8837328..9263680 100644 --- a/teressa-copeland-homes/drizzle/meta/_journal.json +++ b/teressa-copeland-homes/drizzle/meta/_journal.json @@ -50,6 +50,13 @@ "when": 1774115178000, "tag": "0006_type_discriminant", "breakpoints": true + }, + { + "idx": 7, + "version": "7", + "when": 1774116777513, + "tag": "0007_equal_nekra", + "breakpoints": true } ] } \ No newline at end of file diff --git a/teressa-copeland-homes/src/lib/actions/clients.ts b/teressa-copeland-homes/src/lib/actions/clients.ts index d503360..464b1f8 100644 --- a/teressa-copeland-homes/src/lib/actions/clients.ts +++ b/teressa-copeland-homes/src/lib/actions/clients.ts @@ -10,6 +10,7 @@ import { eq } from "drizzle-orm"; const clientSchema = z.object({ name: z.string().min(1, "Name is required"), email: z.string().email("Valid email address required"), + propertyAddress: z.string().optional(), }); export async function createClient( @@ -24,6 +25,7 @@ export async function createClient( const parsed = clientSchema.safeParse({ name: formData.get("name"), email: formData.get("email"), + propertyAddress: formData.get("propertyAddress"), }); if (!parsed.success) { @@ -33,6 +35,7 @@ export async function createClient( await db.insert(clients).values({ name: parsed.data.name, email: parsed.data.email, + propertyAddress: parsed.data.propertyAddress || null, }); revalidatePath("/portal/clients"); @@ -52,6 +55,7 @@ export async function updateClient( const parsed = clientSchema.safeParse({ name: formData.get("name"), email: formData.get("email"), + propertyAddress: formData.get("propertyAddress"), }); if (!parsed.success) { @@ -60,7 +64,7 @@ export async function updateClient( await db .update(clients) - .set({ name: parsed.data.name, email: parsed.data.email, updatedAt: new Date() }) + .set({ name: parsed.data.name, email: parsed.data.email, propertyAddress: parsed.data.propertyAddress || null, updatedAt: new Date() }) .where(eq(clients.id, id)); revalidatePath("/portal/clients"); diff --git a/teressa-copeland-homes/src/lib/db/schema.ts b/teressa-copeland-homes/src/lib/db/schema.ts index a993125..23c10ba 100644 --- a/teressa-copeland-homes/src/lib/db/schema.ts +++ b/teressa-copeland-homes/src/lib/db/schema.ts @@ -55,6 +55,7 @@ export const clients = pgTable("clients", { id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), name: text("name").notNull(), email: text("email").notNull(), + propertyAddress: text("property_address"), createdAt: timestamp("created_at").defaultNow().notNull(), updatedAt: timestamp("updated_at").defaultNow().notNull(), });