diff --git a/src/db/migrations/20251208135406_add-tenant-id-to-uq-in-synced-contacts.sql b/src/db/migrations/20251208135406_add-tenant-id-to-uq-in-synced-contacts.sql new file mode 100644 index 0000000..3ba9da4 --- /dev/null +++ b/src/db/migrations/20251208135406_add-tenant-id-to-uq-in-synced-contacts.sql @@ -0,0 +1,2 @@ +DROP INDEX "uq_synced_contacts_portal_id_client_or_company_id"; +CREATE UNIQUE INDEX "uq_synced_contacts_portal_id_tenant_id_client_or_company_id" ON "synced_contacts" USING btree ("portal_id","tenant_id","client_or_company_id"); \ No newline at end of file diff --git a/src/db/migrations/meta/20251208135406_snapshot.json b/src/db/migrations/meta/20251208135406_snapshot.json new file mode 100644 index 0000000..50014b0 --- /dev/null +++ b/src/db/migrations/meta/20251208135406_snapshot.json @@ -0,0 +1,908 @@ +{ + "id": "fc6fd1e3-3d71-4828-b716-3110ae7270f0", + "prevId": "e2fb5499-e76d-4c32-98dc-98c1d3a6ec8a", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.failed_syncs": { + "name": "failed_syncs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "portal_id": { + "name": "portal_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "failed_syncs_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "varchar(1024)", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "uq_failed_syncs_resource_id": { + "name": "uq_failed_syncs_resource_id", + "columns": [ + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.settings": { + "name": "settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "portal_id": { + "name": "portal_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sync_products_automatically": { + "name": "sync_products_automatically", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "add_absorbed_fees": { + "name": "add_absorbed_fees", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "use_company_name": { + "name": "use_company_name", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_sync_enabled": { + "name": "is_sync_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "initial_invoice_settings_mapping": { + "name": "initial_invoice_settings_mapping", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "initial_product_settings_mapping": { + "name": "initial_product_settings_mapping", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "uq_settings_portal_id_tenant_id": { + "name": "uq_settings_portal_id_tenant_id", + "columns": [ + { + "expression": "portal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sync_logs": { + "name": "sync_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "portal_id": { + "name": "portal_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sync_date": { + "name": "sync_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "sync_logs_event_type", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "sync_logs_status", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "sync_logs_entity_type", + "primaryKey": false, + "notNull": true + }, + "copilot_id": { + "name": "copilot_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "xero_id": { + "name": "xero_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "invoice_number": { + "name": "invoice_number", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "customer_name": { + "name": "customer_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "customer_email": { + "name": "customer_email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "amount": { + "name": "amount", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "tax_amount": { + "name": "tax_amount", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "fee_amount": { + "name": "fee_amount", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "product_name": { + "name": "product_name", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "product_price": { + "name": "product_price", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "xero_item_name": { + "name": "xero_item_name", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_sync_logs_portal_id_tenant_id_created_at": { + "name": "idx_sync_logs_portal_id_tenant_id_created_at", + "columns": [ + { + "expression": "portal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" desc", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.synced_contacts": { + "name": "synced_contacts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "portal_id": { + "name": "portal_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "client_or_company_id": { + "name": "client_or_company_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_type": { + "name": "user_type", + "type": "synced_contacts_contact_user_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'CLIENT'" + }, + "contact_id": { + "name": "contact_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "uq_synced_contacts_portal_id_tenant_id_client_or_company_id": { + "name": "uq_synced_contacts_portal_id_tenant_id_client_or_company_id", + "columns": [ + { + "expression": "portal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "client_or_company_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.synced_invoices": { + "name": "synced_invoices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "portal_id": { + "name": "portal_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "copilot_invoice_id": { + "name": "copilot_invoice_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "xero_invoice_id": { + "name": "xero_invoice_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "synced_invoices_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "uq_synced_invoices_portal_id_copilot_invoice_id": { + "name": "uq_synced_invoices_portal_id_copilot_invoice_id", + "columns": [ + { + "expression": "portal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "copilot_invoice_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.synced_items": { + "name": "synced_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "portal_id": { + "name": "portal_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "product_id": { + "name": "product_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "price_id": { + "name": "price_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "item_id": { + "name": "item_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "uq_synced_items_portal_id_product_id_price_id": { + "name": "uq_synced_items_portal_id_product_id_price_id", + "columns": [ + { + "expression": "portal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "product_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "price_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.synced_payments": { + "name": "synced_payments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "portal_id": { + "name": "portal_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "copilot_invoice_id": { + "name": "copilot_invoice_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "xero_invoice_id": { + "name": "xero_invoice_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "copilot_payment_id": { + "name": "copilot_payment_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "xero_payment_id": { + "name": "xero_payment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "synced_payments_payment_type", + "primaryKey": false, + "notNull": false, + "default": "'payment'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "uq_synced_payments_portal_id_tenant_id_copilot_invoice_id": { + "name": "uq_synced_payments_portal_id_tenant_id_copilot_invoice_id", + "columns": [ + { + "expression": "portal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "copilot_invoice_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "uq_synced_payments_xero_payment_id": { + "name": "uq_synced_payments_xero_payment_id", + "columns": [ + { + "expression": "xero_payment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.xero_connections": { + "name": "xero_connections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "portal_id": { + "name": "portal_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "token_set": { + "name": "token_set", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "initiated_by": { + "name": "initiated_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "uq_xero_connections_portal_id": { + "name": "uq_xero_connections_portal_id", + "columns": [ + { + "expression": "portal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.failed_syncs_type": { + "name": "failed_syncs_type", + "schema": "public", + "values": [ + "invoice.created", + "invoice.updated", + "invoice.paid", + "invoice.voided", + "invoice.deleted", + "product.updated", + "price.created", + "payment.succeeded" + ] + }, + "public.sync_logs_entity_type": { + "name": "sync_logs_entity_type", + "schema": "public", + "values": ["invoice", "customer", "product", "expense"] + }, + "public.sync_logs_event_type": { + "name": "sync_logs_event_type", + "schema": "public", + "values": ["created", "mapped", "unmapped", "paid", "voided", "updated", "deleted"] + }, + "public.sync_logs_status": { + "name": "sync_logs_status", + "schema": "public", + "values": ["success", "failed", "info"] + }, + "public.synced_invoices_status": { + "name": "synced_invoices_status", + "schema": "public", + "values": ["pending", "failed", "success"] + }, + "public.synced_payments_payment_type": { + "name": "synced_payments_payment_type", + "schema": "public", + "values": ["payment", "expense"] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/src/db/migrations/meta/_journal.json b/src/db/migrations/meta/_journal.json index 88f437f..4fd9ccf 100644 --- a/src/db/migrations/meta/_journal.json +++ b/src/db/migrations/meta/_journal.json @@ -64,6 +64,13 @@ "when": 1764932145924, "tag": "20251205105545_add-invoice-updated-field-to-failed-syncs", "breakpoints": false + }, + { + "idx": 9, + "version": "7", + "when": 1765202046215, + "tag": "20251208135406_add-tenant-id-to-uq-in-synced-contacts", + "breakpoints": false } ] } diff --git a/src/db/schema/syncedContacts.schema.ts b/src/db/schema/syncedContacts.schema.ts index c334ab3..9da88f5 100644 --- a/src/db/schema/syncedContacts.schema.ts +++ b/src/db/schema/syncedContacts.schema.ts @@ -29,8 +29,9 @@ export const syncedContacts = pgTable( }, (t) => [ // Each client within a portal must have ONLY ONE mapping to a contact - uniqueIndex('uq_synced_contacts_portal_id_client_or_company_id').on( + uniqueIndex('uq_synced_contacts_portal_id_tenant_id_client_or_company_id').on( t.portalId, + t.tenantId, t.clientOrCompanyId, ), ],