Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/features/auth/lib/Auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ class AuthService extends BaseService {
tokenSet = await dbx.handleDropboxCallback(urlParams)

// Need to get the user's account info to get the Team Root Namespace ID. This step makes sure we are accessing the root folder that includes both personal and team folder
console.info('AuthService#handleDropboxCallback :: Getting account info')
logger.log('AuthService#handleDropboxCallback :: Getting account info')
const dbxClient = new DropboxClient(tokenSet.refreshToken).getDropboxClient()
const accountInfo = await dbxClient.usersGetCurrentAccount()
console.info('AuthService#handleDropboxCallback :: Account info', accountInfo)
logger.log('AuthService#handleDropboxCallback :: Account info', accountInfo)

// The root_namespace_id is strictly available on root_info
rootNamespaceId = accountInfo.result.root_info?.root_namespace_id
Expand Down
10 changes: 5 additions & 5 deletions src/features/dropbox/lib/Dropbox.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export class DropboxService extends AuthenticatedDropboxService {
path: string,
dbxClient: Dropbox,
) {
console.info('DropboxService#searchChildrenFolders :: Searching children folders')
logger.log('DropboxService#searchChildrenFolders :: Searching children folders')
const isSharedFolder = !!folderResult.sharing_info?.shared_folder_id
const prefixPath = isSharedFolder ? path : undefined

Expand All @@ -81,20 +81,20 @@ export class DropboxService extends AuthenticatedDropboxService {
* To get the subfolder of the result folder, we use filesListFolder with the namespace_id of the result folder.
*/
async _searchForFolder({ dbxClient, search }: { dbxClient: Dropbox; search: string }) {
logger.info('DropboxService#getFolderTree :: Searching folder in Dropbox... Query: ', search)
logger.log('DropboxService#getFolderTree :: Searching folder in Dropbox... Query: ', search)
const sanitizedSearch = replaceSpecialCharactersWithSpace(search)
if (!sanitizedSearch) return []

const { path, folder: query } = splitPathAndFolder(sanitizedSearch)
console.info({ path, query })
logger.log({ path, query })

let tempPath = path
let folderResult: files.FolderMetadataReference | undefined

const formattedFolders: Partial<Folder>[] = []

if (query !== '') {
console.info('DropboxService#searchForFolder :: Query is available. Searching for folder')
logger.log('DropboxService#searchForFolder :: Query is available. Searching for folder')
const searchResponse = await dbxClient.filesSearchV2({
query,
options: {
Expand All @@ -117,7 +117,7 @@ export class DropboxService extends AuthenticatedDropboxService {
const metadata = matchMetadata.metadata
folderResult = metadata['.tag'] === ObjectType.FOLDER ? metadata : undefined
} else {
console.info('DropboxService#searchForFolder :: Query is empty. Getting folder metadata')
logger.log('DropboxService#searchForFolder :: Query is empty. Getting folder metadata')

const metaDataResp = await dbxClient.filesGetMetadata({ path: tempPath })
if (metaDataResp.status !== httpStatus.OK) {
Expand Down
18 changes: 9 additions & 9 deletions src/features/sync/lib/MapFiles.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ type MarkUpdatedPayload = Omit<

export class MapFilesService extends AuthenticatedDropboxService {
async getSingleFileMap(where: WhereClause): Promise<FileSyncSelectType | undefined> {
logger.info('MapFilesService#getSingleFileMap :: Getting single file map where', where.getSQL())
logger.info('MapFilesService#getSingleFileMap :: Getting single file map')

const results = await db.query.fileFolderSync.findFirst({
where,
Expand All @@ -62,7 +62,7 @@ export class MapFilesService extends AuthenticatedDropboxService {
}

async getAllFileMaps(where: WhereClause): Promise<FileSyncSelectType[]> {
logger.info('MapFilesService#getAllFileMaps :: Getting all file maps where', where.getSQL())
logger.info('MapFilesService#getAllFileMaps :: Getting all file maps')

const results = await db.query.fileFolderSync.findMany({
where: (fileFolderSync, { eq }) =>
Expand Down Expand Up @@ -178,7 +178,7 @@ export class MapFilesService extends AuthenticatedDropboxService {
payload: FileSyncUpdatePayload,
condition: WhereClause,
): Promise<FileSyncSelectType> {
logger.info('MapFilesService#updateFileMap :: Updating file map', payload, condition.getSQL())
logger.info('MapFilesService#updateFileMap :: Updating file map', payload)

const [connection] = await db
.update(fileFolderSync)
Expand Down Expand Up @@ -528,7 +528,7 @@ export class MapFilesService extends AuthenticatedDropboxService {
}

async getAllChannelMaps(where?: WhereClause): Promise<ChannelSyncSelectType[]> {
logger.info('MapFilesService#getAllChannelMaps :: Getting all channel maps', where?.getSQL())
logger.info('MapFilesService#getAllChannelMaps :: Getting all channel maps')

const results = await db.query.channelSync.findMany({
where: (channelSync, { eq }) =>
Expand Down Expand Up @@ -684,7 +684,7 @@ export class MapFilesService extends AuthenticatedDropboxService {

// Bulk soft-delete stale channel maps
if (staleChannelMapIds.length > 0) {
console.info('Soft delete channel maps and make them inactive: ', staleChannelMapIds)
logger.info('Soft delete channel maps and make them inactive: ', staleChannelMapIds)
await this.deleteChannelMapsByIds(staleChannelMapIds)
}

Expand Down Expand Up @@ -719,23 +719,23 @@ export class MapFilesService extends AuthenticatedDropboxService {

if (fileChannel.membershipType === FileChannelMembership.COMPANY) {
if (!fileChannel.companyId) {
console.error('Company id not found')
logger.error('Company id not found')
return null
}
const company = companyMap.get(fileChannel.companyId)
if (!company) {
console.error('Company not found in batch response', fileChannel.companyId)
logger.error('Company not found in batch response', fileChannel.companyId)
return null
}
fileChannelValue = [{ id: company.id, companyId: company.id, object: 'company' as const }]
} else {
if (!fileChannel.clientId) {
console.error('Client id not found')
logger.error('Client id not found')
return null
}
const client = clientMap.get(fileChannel.clientId)
if (!client) {
console.error('Client not found in batch response', fileChannel.clientId)
logger.error('Client not found in batch response', fileChannel.clientId)
return null
}
fileChannelValue = [
Expand Down
22 changes: 9 additions & 13 deletions src/features/sync/lib/Sync.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -515,7 +515,7 @@ export class SyncService extends AuthenticatedDropboxService {
error.status === 400 &&
error.body.message === 'Folder already exists'
) {
console.info({ message: error.body.message, path: itemPath })
logger.info({ message: error.body.message, path: itemPath })
// Row exists (concurrent winner) → just stamp dbxFileId. Otherwise recover the
// folder's id + path from Assembly so children resolve under it, not a duplicate.
const existing = await this.mapFilesService.getDbxMappedFileFromPath(
Expand Down Expand Up @@ -543,7 +543,7 @@ export class SyncService extends AuthenticatedDropboxService {
}
return
}
console.error(
logger.error(
`SyncService#createFolderInAssembly. Upload failed. Channel ID: ${assemblyChannelId}. Path: ${itemPath}`,
)
throw error
Expand Down Expand Up @@ -773,7 +773,7 @@ export class SyncService extends AuthenticatedDropboxService {
logger.info('SyncService#uploadFileInAssembly :: File uploaded to Assembly', dbxPath)

if (fileUploadResp.status !== httpStatus.OK) {
console.error({ error: await fileUploadResp.json() })
logger.error({ error: await fileUploadResp.json() })
throw new Error('SyncService#uploadFileInAssemnly. Failed to upload file to assembly')
}
}
Expand All @@ -795,11 +795,7 @@ export class SyncService extends AuthenticatedDropboxService {
eq(fileFolderSync.itemPath, basePath),
) as WhereClause
try {
logger.info(
'SyncService#handleFolderCreatedCase :: Updating dbxFileId',
entryId,
fileMapCondition.getSQL(),
)
logger.info('SyncService#handleFolderCreatedCase :: Updating dbxFileId', entryId)
} catch (e) {
logger.info(e)
}
Expand Down Expand Up @@ -901,7 +897,7 @@ export class SyncService extends AuthenticatedDropboxService {
fileType: ObjectTypeValue,
file: CopilotFileRetrieve,
): Promise<{ dbxFileId: string; contentHash?: string } | undefined> {
console.info(`SyncService#createAndUploadFileInDropbox. Channel ID: ${file.channelId}`)
logger.log(`SyncService#createAndUploadFileInDropbox. Channel ID: ${file.channelId}`)

const dbxClient = this.dbxClient.getDropboxClient()
const dbxFilePath = `${dbxRootPath}/${file.path}`
Expand Down Expand Up @@ -931,7 +927,7 @@ export class SyncService extends AuthenticatedDropboxService {
error.status === 409 &&
dbxError?.error?.path?.['.tag'] === 'not_found'
if (!isNotFound) {
console.error(`SyncService#createAndUploadFileInDropbox. Channel ID: ${file.channelId}`)
logger.error(`SyncService#createAndUploadFileInDropbox. Channel ID: ${file.channelId}`)
throw error
}
}
Expand Down Expand Up @@ -960,7 +956,7 @@ export class SyncService extends AuthenticatedDropboxService {
}

if (existing) {
console.info(
logger.info(
`SyncService#createAndUploadFileInDropbox. File exists but didn't received required file tag. Type: ${existing['.tag']}. Channel ID: ${file.channelId}`,
)
return
Expand All @@ -979,7 +975,7 @@ export class SyncService extends AuthenticatedDropboxService {
logger.info('SyncService#createAndUploadFileInDropbox :: File created', dbxFilePath)
return await this.uploadFileInDropbox(file, dbxFilePath)
}
console.info(
logger.info(
`SyncService#createAndUploadFileInDropbox. File type out of bound. Type: ${fileType}. Channel ID: ${file.channelId}`,
)
}
Expand All @@ -1003,7 +999,7 @@ export class SyncService extends AuthenticatedDropboxService {
contentHash: dbxResponse.contentHash,
}
}
console.error(
logger.error(
`SyncService#uploadFileInDropbox. Assembly file with Id: ${file.id} has no download url. Channel ID: ${file.channelId}`,
)
throw new Error('File not found')
Expand Down
4 changes: 2 additions & 2 deletions src/features/webhook/assembly/api/webhook.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const handleWebhookEvent = async (req: NextRequest) => {
const connection = await dropboxConnectionService.getConnectionForWorkspace()

if (!connection.status) {
console.info(`Sync is not enabled for this workspace. Skipping webhook event`)
logger.info(`Sync is not enabled for this workspace. Skipping webhook event`)
return NextResponse.json({})
}

Expand All @@ -37,7 +37,7 @@ export const handleWebhookEvent = async (req: NextRequest) => {
rootNamespaceId: connection.rootNamespaceId,
})
const webhookEvent = assemblyWebhookService.parseWebhook(rawBody)
logger.info(`Event triggered. ${JSON.stringify(webhookEvent)}`)
logger.info('Event triggered.', webhookEvent)

const eventType = assemblyWebhookService.validateHandleableEvent(webhookEvent)
if (!eventType) {
Expand Down
3 changes: 2 additions & 1 deletion src/features/webhook/dropbox/api/webhook.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import status from 'http-status'
import { type NextRequest, NextResponse } from 'next/server'
import env from '@/config/server.env'
import { DropboxWebhook } from '@/features/webhook/dropbox/lib/webhook.service'
import logger from '@/lib/logger'
import { sleep } from '@/utils/sleep'

export const handleWebhookUrlVerification = (req: NextRequest) => {
Expand All @@ -16,7 +17,7 @@ export const handleWebhookUrlVerification = (req: NextRequest) => {
},
})
} catch (error: unknown) {
console.error('Webhook verification error:', error)
logger.error('Webhook verification error:', error)
return NextResponse.json(
{ error: 'Something went wrong' },
{ status: status.INTERNAL_SERVER_ERROR },
Expand Down
14 changes: 6 additions & 8 deletions src/features/webhook/dropbox/lib/webhook.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export class DropboxWebhook {

// Skip if already pending — cron will handle it
if (connection.pendingWebhook) {
console.info(`Webhook skipped for account ${account}, already has pending webhook`)
logger.info(`Webhook skipped for account ${account}, already has pending webhook`)
continue
}

Expand All @@ -49,7 +49,7 @@ export class DropboxWebhook {
.update(dropboxConnections)
.set({ pendingWebhook: true })
.where(eq(dropboxConnections.id, connection.id))
console.info(`Webhook debounced for account ${account}, marked as pending`)
logger.info(`Webhook debounced for account ${account}, marked as pending`)
} else {
await processDropboxChanges.trigger(account, { concurrencyKey: account })
}
Expand All @@ -60,7 +60,7 @@ export class DropboxWebhook {
const connection = await this.getActiveConnection(accountId)

if (!connection || !connection.refreshToken) {
console.error(
logger.error(
`DropboxWebhook#fetchDropboxChanges :: Connection is not valid for Dropbox accountId: ${accountId}`,
)
return
Expand Down Expand Up @@ -127,7 +127,7 @@ export class DropboxWebhook {
dbxClient: Dropbox,
): Promise<boolean> {
try {
console.info(
logger.info(
`WebhookService#handleDbxRootPathMove. Root path: ${channel.dbxRootPath}. Assembly file channel ID: ${channel.assemblyChannelId} Checking if the root path exists...`,
)
const response = await this.getDropboxFileMetadata(channel.dbxRootPath, dbxClient)
Expand All @@ -139,7 +139,7 @@ export class DropboxWebhook {
error,
)
if (error instanceof DropboxResponseError && error.status === 409) {
console.info(
logger.info(
'WebhookService#handleDbxRootPathMove :: Root path not found',
channel.dbxRootPath,
)
Expand Down Expand Up @@ -197,9 +197,7 @@ export class DropboxWebhook {
user: User,
connectionToken: DropboxConnectionTokens,
) {
console.info(
`webhookService#processChannelChanges. ChannelId: ${channel.id} ${JSON.stringify(channel)}`,
)
logger.log(`WebhookService#processChannelChanges. ChannelId: ${channel.id}`)
const { id: channelSyncId, dbxRootPath, assemblyChannelId, dbxCursor } = channel
let hasMore = true
let currentCursor = dbxCursor ?? ''
Expand Down
6 changes: 3 additions & 3 deletions src/features/webhook/dropbox/utils/getDropboxChanges.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Dropbox } from 'dropbox'
import type { MapFilesService } from '@/features/sync/lib/MapFiles.service'
import { DropboxFileListFolderResultEntriesSchema } from '@/features/sync/types'
import logger from '@/lib/logger'

export async function getDropboxChanges(
cursor: string,
Expand Down Expand Up @@ -34,8 +35,7 @@ export async function getDropboxChanges(
const parsed = DropboxFileListFolderResultEntriesSchema.safeParse(entriesWithId)

if (!parsed.success) {
console.info(`Entries payload: ${JSON.stringify(entriesWithId)}`)
console.error('Invalid Dropbox response entries:', parsed.error)
logger.error('Invalid Dropbox response entries:', parsed.error)
// return
throw new Error('Invalid Dropbox entries format')
}
Expand All @@ -50,7 +50,7 @@ export async function getDropboxChanges(
hasMore: response.result.has_more,
}
} catch (error) {
console.error('Error fetching Dropbox changes:', error)
logger.error('Error fetching Dropbox changes:', error)
throw error
}
}
64 changes: 64 additions & 0 deletions src/lib/__tests__/logger.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import { logger } from '@/lib/logger'

afterEach(() => {
vi.restoreAllMocks()
delete process.env.LOG_LEVEL
})

describe('logger', () => {
it('caps object depth so nested payloads are not fully dumped', () => {
const spy = vi.spyOn(console, 'info').mockImplementation(() => undefined)
logger.info({ a: { b: { c: { secret: 'deep-value' } } } })
const out = spy.mock.calls[0][0] as string
expect(out).not.toContain('deep-value')
expect(out).toContain('[Object]')
})

it('truncates long arrays instead of dumping every element', () => {
const spy = vi.spyOn(console, 'info').mockImplementation(() => undefined)
logger.info(Array.from({ length: 100 }, (_, i) => i))
expect(spy.mock.calls[0][0] as string).toContain('more items')
})

it('passes string args through unchanged', () => {
const spy = vi.spyOn(console, 'info').mockImplementation(() => undefined)
logger.info('hello', 'world')
expect(spy).toHaveBeenCalledWith('hello world')
})

it('suppresses levels below LOG_LEVEL', () => {
process.env.LOG_LEVEL = 'warn'
const info = vi.spyOn(console, 'info').mockImplementation(() => undefined)
const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
logger.info('quiet')
logger.warn('loud')
expect(info).not.toHaveBeenCalled()
expect(warn).toHaveBeenCalled()
})

it('defaults to info, suppressing the verbose log level', () => {
const log = vi.spyOn(console, 'log').mockImplementation(() => undefined)
const info = vi.spyOn(console, 'info').mockImplementation(() => undefined)
logger.log('debug detail')
logger.info('operational')
expect(log).not.toHaveBeenCalled()
expect(info).toHaveBeenCalled()
})

it('truncates long strings nested in an object', () => {
const spy = vi.spyOn(console, 'info').mockImplementation(() => undefined)
logger.info({ message: 'x'.repeat(1000) })
expect(spy.mock.calls[0][0] as string).toContain('more characters')
})

it('always prints error, and treats an invalid LOG_LEVEL as the default', () => {
process.env.LOG_LEVEL = 'BOGUS' // unrecognized → falls back to info
const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
const error = vi.spyOn(console, 'error').mockImplementation(() => undefined)
logger.warn('kept at info default')
logger.error('always')
expect(warn).toHaveBeenCalled()
expect(error).toHaveBeenCalled()
})
})
Loading
Loading