diff --git a/src/db/migrations/20260203112606_add-uq-to-channel-sync.sql b/src/db/migrations/20260203112606_add-uq-to-channel-sync.sql new file mode 100644 index 0000000..29e5edd --- /dev/null +++ b/src/db/migrations/20260203112606_add-uq-to-channel-sync.sql @@ -0,0 +1,2 @@ +DROP INDEX IF EXISTS "uq_channel_sync_channel_id_dbx_root_path"; +CREATE UNIQUE INDEX IF NOT EXISTS "uq_channel_sync__channel_id_dbx_root_path" ON "channel_sync" USING btree ("assembly_channel_id","dbx_root_path") WHERE "channel_sync"."deleted_at" is null; \ No newline at end of file diff --git a/src/db/migrations/meta/20260203112606_snapshot.json b/src/db/migrations/meta/20260203112606_snapshot.json new file mode 100644 index 0000000..39aa7c1 --- /dev/null +++ b/src/db/migrations/meta/20260203112606_snapshot.json @@ -0,0 +1,503 @@ +{ + "id": "782a75df-3185-4302-9333-9ee92f0cde09", + "prevId": "8ac16056-a07e-4415-8097-fadd9df642b6", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.assembly_webhook_records": { + "name": "assembly_webhook_records", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "portal_id": { + "name": "portal_id", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "assembly_webhook_events_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "assembly_channel_id": { + "name": "assembly_channel_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "file_id": { + "name": "file_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "triggered_at": { + "name": "triggered_at", + "type": "timestamp with time zone", + "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_all_columns_combined": { + "name": "uq_all_columns_combined", + "columns": [ + { + "expression": "portal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assembly_channel_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "triggered_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.channel_sync": { + "name": "channel_sync", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "portal_id": { + "name": "portal_id", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "dbx_account_id": { + "name": "dbx_account_id", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "assembly_channel_id": { + "name": "assembly_channel_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "dbx_root_path": { + "name": "dbx_root_path", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "dbx_root_id": { + "name": "dbx_root_id", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "dbx_cursor": { + "name": "dbx_cursor", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "total_files_count": { + "name": "total_files_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "synced_files_count": { + "name": "synced_files_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "timestamp with time zone", + "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()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_channel_sync_portal_id_dbxAccount_id_deleted_at": { + "name": "idx_channel_sync_portal_id_dbxAccount_id_deleted_at", + "columns": [ + { + "expression": "portal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "dbx_account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "first" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "uq_channel_sync__channel_id_dbx_root_path": { + "name": "uq_channel_sync__channel_id_dbx_root_path", + "columns": [ + { + "expression": "assembly_channel_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "dbx_root_path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"channel_sync\".\"deleted_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_channel_sync_portal_id_deleted_at_created_at": { + "name": "idx_channel_sync_portal_id_deleted_at_created_at", + "columns": [ + { + "expression": "portal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "first" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dropbox_connections": { + "name": "dropbox_connections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "portal_id": { + "name": "portal_id", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "root_namespace_id": { + "name": "root_namespace_id", + "type": "varchar(100)", + "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_dropbox_connections_portal_id": { + "name": "uq_dropbox_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 + }, + "public.file_folder_sync": { + "name": "file_folder_sync", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "portal_id": { + "name": "portal_id", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "channel_sync_id": { + "name": "channel_sync_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "item_path": { + "name": "item_path", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "object": { + "name": "object", + "type": "object_types", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'file'" + }, + "content_hash": { + "name": "content_hash", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "dbx_file_id": { + "name": "dbx_file_id", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "assembly_file_id": { + "name": "assembly_file_id", + "type": "uuid", + "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()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "file_folder_sync_channel_sync_id_channel_sync_id_fk": { + "name": "file_folder_sync_channel_sync_id_channel_sync_id_fk", + "tableFrom": "file_folder_sync", + "tableTo": "channel_sync", + "columnsFrom": ["channel_sync_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.assembly_webhook_events_enum": { + "name": "assembly_webhook_events_enum", + "schema": "public", + "values": [ + "file.created", + "file.updated", + "file.deleted", + "folder.created", + "folder.updated", + "folder.deleted" + ] + }, + "public.object_types": { + "name": "object_types", + "schema": "public", + "values": ["file", "folder"] + } + }, + "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 c505617..6c48860 100644 --- a/src/db/migrations/meta/_journal.json +++ b/src/db/migrations/meta/_journal.json @@ -64,6 +64,13 @@ "when": 1765258299790, "tag": "20251209053139_add_combined_index_in_channel_sync", "breakpoints": false + }, + { + "idx": 9, + "version": "7", + "when": 1770117966182, + "tag": "20260203112606_add-uq-to-channel-sync", + "breakpoints": false } ] } diff --git a/src/db/schema/channelSync.schema.ts b/src/db/schema/channelSync.schema.ts index f460014..e4f1559 100644 --- a/src/db/schema/channelSync.schema.ts +++ b/src/db/schema/channelSync.schema.ts @@ -1,4 +1,4 @@ -import { type InferInsertModel, type InferSelectModel, relations } from 'drizzle-orm' +import { type InferInsertModel, type InferSelectModel, isNull, relations } from 'drizzle-orm' import { boolean, index, @@ -38,10 +38,9 @@ export const channelSync = pgTable( table.dbxAccountId, table.deletedAt.nullsFirst(), ), - uniqueIndex('uq_channel_sync_channel_id_dbx_root_path').on( - table.assemblyChannelId, - table.dbxRootPath, - ), + uniqueIndex('uq_channel_sync__channel_id_dbx_root_path') + .on(table.assemblyChannelId, table.dbxRootPath) + .where(isNull(table.deletedAt)), index('idx_channel_sync_portal_id_deleted_at_created_at').on( table.portalId, table.createdAt.asc(), diff --git a/src/features/sync/lib/MapFiles.service.ts b/src/features/sync/lib/MapFiles.service.ts index c7d6c06..a43dcb8 100644 --- a/src/features/sync/lib/MapFiles.service.ts +++ b/src/features/sync/lib/MapFiles.service.ts @@ -240,6 +240,7 @@ export class MapFilesService extends AuthenticatedDropboxService { eq(channelSync.dbxAccountId, this.connectionToken.accountId), eq(channelSync.assemblyChannelId, assemblyChannelId), eq(channelSync.dbxRootPath, dbxRootPath), + isNull(channelSync.deletedAt), ), ) .returning()