From be62d63bcaa385608bfe9951978886c082dd48bb Mon Sep 17 00:00:00 2001 From: SandipBajracharya Date: Wed, 4 Feb 2026 13:37:52 +0545 Subject: [PATCH 1/3] fix(OUT-3066): sanitize filename before creating in Assembly --- src/lib/copilot/CopilotAPI.ts | 3 ++- src/utils/filePath.ts | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/lib/copilot/CopilotAPI.ts b/src/lib/copilot/CopilotAPI.ts index 0d08489..b43b31d 100644 --- a/src/lib/copilot/CopilotAPI.ts +++ b/src/lib/copilot/CopilotAPI.ts @@ -43,6 +43,7 @@ import { } from '@/lib/copilot/types' import logger from '@/lib/logger' import { withRetry } from '@/lib/withRetry' +import { sanitizeFileNameForAssembly } from '@/utils/filePath' export class CopilotAPI { readonly copilot: SDK @@ -191,7 +192,7 @@ export class CopilotAPI { const createFileResponse = await this.copilot.createFile({ fileType, requestBody: { - path, + path: sanitizeFileNameForAssembly(path), channelId, }, }) diff --git a/src/utils/filePath.ts b/src/utils/filePath.ts index 4bf3f57..f7a3fb7 100644 --- a/src/utils/filePath.ts +++ b/src/utils/filePath.ts @@ -79,3 +79,19 @@ export function splitPathAndFolder(fullPath: string): { path: string; folder: st export function sanitizePath(path: string) { return path.replace(/^\/+/, '') } + +export function sanitizeFileNameForAssembly(filename: string): string { + const lastDotIndex = filename.lastIndexOf('.') + + // if there's no extension, sanitize the whole filename + if (lastDotIndex === -1) { + return filename.replace(/[^a-zA-Z0-9_-]/g, ' ') + } + + const name = filename.slice(0, lastDotIndex) + const extension = filename.slice(lastDotIndex + 1) + + const sanitizedName = name.replace(/[^a-zA-Z0-9_-]/g, ' ') + + return `${sanitizedName}.${extension}` +} From a288a8f205a2c9504b505747351ad97927452411 Mon Sep 17 00:00:00 2001 From: SandipBajracharya Date: Wed, 4 Feb 2026 13:38:45 +0545 Subject: [PATCH 2/3] docs(OUT-3066): log filepath when uploading to assembly --- src/features/sync/lib/Sync.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/features/sync/lib/Sync.service.ts b/src/features/sync/lib/Sync.service.ts index f846deb..b28252a 100644 --- a/src/features/sync/lib/Sync.service.ts +++ b/src/features/sync/lib/Sync.service.ts @@ -191,7 +191,7 @@ export class SyncService extends AuthenticatedDropboxService { error.status === 400 && error.body.message === 'Folder already exists' ) { - console.info({ message: error.body.message }) + console.info({ message: error.body.message, path: itemPath }) await this.handleFolderCreatedCase( lastItem, tempFileType, @@ -202,7 +202,7 @@ export class SyncService extends AuthenticatedDropboxService { return } console.error( - `SyncService#createAndUploadFileToAssembly. Upload failed. Channel ID: ${assemblyChannelId}`, + `SyncService#createAndUploadFileToAssembly. Upload failed. Channel ID: ${assemblyChannelId}. Path: ${itemPath}`, ) throw error } From d510fb677460b0c65cff53ae254c34b4fda38c04 Mon Sep 17 00:00:00 2001 From: SandipBajracharya Date: Wed, 4 Feb 2026 17:19:35 +0545 Subject: [PATCH 3/3] fix(OUT-3066): convert accents and allow dots in filename --- package.json | 2 ++ pnpm-lock.yaml | 17 +++++++++++++++++ src/utils/filePath.ts | 20 +++++++------------- 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 778d3ac..39d52ef 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "react-time-ago": "^7.3.5", "require-in-the-middle": "7.5.2", "tailwind-merge": "^3.3.1", + "unorm": "^1.6.0", "zod": "^4.1.1" }, "devDependencies": { @@ -61,6 +62,7 @@ "@types/react": "^19", "@types/react-dom": "^19", "@types/react-linkify": "^1.0.4", + "@types/unorm": "^1.3.31", "drizzle-kit": "^0.31.4", "husky": "^9.1.7", "lint-staged": "^16.1.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 396c3bd..28b07c7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -89,6 +89,9 @@ importers: tailwind-merge: specifier: ^3.3.1 version: 3.3.1 + unorm: + specifier: ^1.6.0 + version: 1.6.0 zod: specifier: ^4.1.1 version: 4.1.1 @@ -129,6 +132,9 @@ importers: '@types/react-linkify': specifier: ^1.0.4 version: 1.0.4 + '@types/unorm': + specifier: ^1.3.31 + version: 1.3.31 drizzle-kit: specifier: ^0.31.4 version: 0.31.4 @@ -1983,6 +1989,9 @@ packages: '@types/tedious@4.0.14': resolution: {integrity: sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw==} + '@types/unorm@1.3.31': + resolution: {integrity: sha512-qCPX/Lo14ECb9Wkb/1sxdcTQqIiHTVNlaHczGrh2WqMVSlWjfn8Hu7DxraCtBYz1+Ud6Id/d+4OH/hkd+dlnpw==} + '@types/ws@8.18.1': resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} @@ -3867,6 +3876,10 @@ packages: resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} engines: {node: '>=18'} + unorm@1.6.0: + resolution: {integrity: sha512-b2/KCUlYZUeA7JFUuRJZPUtr4gZvBh7tavtv4fvk4+KV9pfGiR6CQAQAWl49ZpR3ts2dk4FYkP7EIgDJoiOLDA==} + engines: {node: '>= 0.4.0'} + unplugin@1.0.1: resolution: {integrity: sha512-aqrHaVBWW1JVKBHmGo33T5TxeL0qWzfvjWokObHA9bYmN7eNDkwOxmLjhioHl9878qDFMAaT51XNroRyuz7WxA==} @@ -5855,6 +5868,8 @@ snapshots: dependencies: '@types/node': 20.19.11 + '@types/unorm@1.3.31': {} + '@types/ws@8.18.1': dependencies: '@types/node': 20.19.11 @@ -7685,6 +7700,8 @@ snapshots: unicorn-magic@0.1.0: {} + unorm@1.6.0: {} + unplugin@1.0.1: dependencies: acorn: 8.15.0 diff --git a/src/utils/filePath.ts b/src/utils/filePath.ts index f7a3fb7..211cc1b 100644 --- a/src/utils/filePath.ts +++ b/src/utils/filePath.ts @@ -1,5 +1,6 @@ import * as p from 'node:path' import dayjs from 'dayjs' +import unorm from 'unorm' import { ObjectType } from '@/db/constants' export function buildPathArray(path: string): string[] { @@ -81,17 +82,10 @@ export function sanitizePath(path: string) { } export function sanitizeFileNameForAssembly(filename: string): string { - const lastDotIndex = filename.lastIndexOf('.') - - // if there's no extension, sanitize the whole filename - if (lastDotIndex === -1) { - return filename.replace(/[^a-zA-Z0-9_-]/g, ' ') - } - - const name = filename.slice(0, lastDotIndex) - const extension = filename.slice(lastDotIndex + 1) - - const sanitizedName = name.replace(/[^a-zA-Z0-9_-]/g, ' ') - - return `${sanitizedName}.${extension}` + return unorm + .nfd(filename) // decompose accents + .replace(/[\u0300-\u036f]/g, '') // remove diacritics + .replace(/[^a-zA-Z0-9._-]/g, '_') // replace special chars with _ + .replace(/_+/g, '_') // collapse multiple _ + .replace(/^_+|_+$/g, '') // trim _ from ends }