Skip to content
Merged
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
30 changes: 16 additions & 14 deletions src/app/(home)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,24 @@ const Home = async ({ searchParams }: PageProps) => {
const connection = await dpxConnectionService.getConnectionForWorkspace()

const userService = new UserService(user)
const users = await userService.getSelectorClientsCompanies()
const workspace = await getWorkspace(token)
let mapList: MapList[] = [],
tempMapList: MapList[] = []

if (connection.refreshToken && connection.accountId) {
const connectionToken = {
refreshToken: connection.refreshToken,
accountId: connection.accountId,
rootNamespaceId: connection.rootNamespaceId,
}
// Fetch user data, workspace, and channel maps in parallel
const mapListPromise =
connection.refreshToken && connection.accountId
? new MapFilesService(user, {
refreshToken: connection.refreshToken,
accountId: connection.accountId,
rootNamespaceId: connection.rootNamespaceId,
}).listFormattedChannelMap()
: Promise.resolve([] as MapList[])

const mapService = new MapFilesService(user, connectionToken)
mapList = await mapService.listFormattedChannelMap()
tempMapList = structuredClone(mapList)
}
const [users, workspace, mapList] = await Promise.all([
userService.getSelectorClientsCompanies(),
getWorkspace(token),
mapListPromise,
])

const tempMapList = structuredClone(mapList)

return (
<AuthContextProvider user={clientUser} connectionStatus={connection.status}>
Expand Down
186 changes: 114 additions & 72 deletions src/features/sync/lib/MapFiles.service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { and, asc, eq, isNotNull, isNull, or, sql } from 'drizzle-orm'
import { and, asc, eq, inArray, isNotNull, isNull, or, sql } from 'drizzle-orm'
import httpStatus from 'http-status'
import { ApiError } from 'node_modules/copilot-node-sdk/dist/codegen/api'
import z from 'zod'
import { MAX_FETCH_COPILOT_RESOURCES } from '@/constants/limits'
import db from '@/db'
import { ObjectType } from '@/db/constants'
import {
Expand All @@ -22,7 +22,6 @@ import type {
MapList,
WhereClause,
} from '@/features/sync/types'
import { copilotBottleneck } from '@/lib/copilot/bottleneck'
import {
type CopilotFileList,
FileChannelMembership,
Expand Down Expand Up @@ -278,8 +277,10 @@ export class MapFilesService extends AuthenticatedDropboxService {
.where(eq(channelSync.id, id))
}

async deleteChannelMapById(id: string) {
logger.info('MapFilesService#deleteChannelMapById :: Deleting channel map', id)
async deleteChannelMapsByIds(ids: string[]) {
if (ids.length === 0) return

logger.info('MapFilesService#deleteChannelMapsByIds :: Deleting channel maps', ids)

await db.transaction(async (tx) => {
const deletedAt = new Date()
Expand All @@ -289,17 +290,17 @@ export class MapFilesService extends AuthenticatedDropboxService {
deletedAt,
status: false,
})
.where(eq(channelSync.id, id))
.where(inArray(channelSync.id, ids))

await tx
.update(fileFolderSync)
.set({
deletedAt,
})
.where(eq(fileFolderSync.channelSyncId, id))
.where(inArray(fileFolderSync.channelSyncId, ids))
})

logger.info('MapFilesService#deleteChannelMapById :: Deleted channel map', id)
logger.info('MapFilesService#deleteChannelMapsByIds :: Deleted channel maps', ids)
}

async getAllChannelMaps(where?: WhereClause): Promise<ChannelSyncSelectType[]> {
Expand Down Expand Up @@ -421,80 +422,121 @@ export class MapFilesService extends AuthenticatedDropboxService {

async listFormattedChannelMap(): Promise<MapList[]> {
const channelMaps = await this.getAllChannelMaps()
if (channelMaps.length === 0) return []

// Batch fetch all file channels in one API call
const fileChannels = await this.copilot.listFileChannels({
limit: MAX_FETCH_COPILOT_RESOURCES,
})
const fileChannelMap = new Map(fileChannels.map((fc) => [fc.id, fc]))

// Collect unique company and client IDs from file channels that match our channel maps
const companyIds = new Set<string>()
const clientIds = new Set<string>()
const staleChannelMapIds: string[] = []

const channelMapPromises = []
for (const channelMap of channelMaps) {
channelMapPromises.push(
copilotBottleneck.schedule(() => {
return this.formatChannelMap(channelMap)
}),
)
const fc = fileChannelMap.get(channelMap.assemblyChannelId)
if (!fc) {
staleChannelMapIds.push(channelMap.id)
continue
}
if (fc.companyId) companyIds.add(fc.companyId)
if (fc.clientId) clientIds.add(fc.clientId)
}

return (await Promise.all(channelMapPromises)).filter((channelMap) => !!channelMap)
}
// Batch fetch all companies and clients in parallel (2 API calls total)
const [companiesResponse, clientsResponse] = await Promise.all([
companyIds.size > 0
? this.copilot.getCompanies({ limit: MAX_FETCH_COPILOT_RESOURCES })
: Promise.resolve({ data: [] }),
clientIds.size > 0
? this.copilot.getClients({ limit: MAX_FETCH_COPILOT_RESOURCES })
: Promise.resolve({ data: [] }),
])

const companyMap = new Map((companiesResponse.data ?? []).map((c) => [c.id, c]))
const clientMap = new Map((clientsResponse.data ?? []).map((c) => [c.id, c]))

// Bulk soft-delete stale channel maps
if (staleChannelMapIds.length > 0) {
console.info('Soft delete channel maps and make them inactive: ', staleChannelMapIds)
await this.deleteChannelMapsByIds(staleChannelMapIds)
}
Comment thread
priosshrsth marked this conversation as resolved.

async formatChannelMap(channelMap: ChannelSyncSelectType): Promise<MapList | null> {
logger.info('MapFilesService#formatChannelMap :: Formatting channel map', channelMap)
// Format all channel maps locally using pre-fetched data
const results: MapList[] = []
for (const channelMap of channelMaps) {
const formatted = this.formatChannelMapFromCache(
channelMap,
fileChannelMap,
companyMap,
clientMap,
)
if (formatted) results.push(formatted)
}

try {
let fileChannelValue: UserCompanySelectorInputValue[]
const fileChannel = await this.copilot.retrieveFileChannel(channelMap.assemblyChannelId)
return results
}

if (fileChannel.membershipType === FileChannelMembership.COMPANY) {
if (!fileChannel.companyId) {
console.error('Company id not found')
return null
}
const companyDetails = await this.copilot.getCompany(fileChannel.companyId)
fileChannelValue = [
{
id: companyDetails.id,
companyId: companyDetails.id,
object: 'company' as const,
},
]
} else {
if (!fileChannel.clientId) {
console.error('Client id not found')
return null
}
const clientDetails = await this.copilot.getClient(fileChannel.clientId)
fileChannelValue = [
{
id: clientDetails.id,
companyId: z.string().parse(fileChannel.companyId),
object: 'client' as const,
},
]
private formatChannelMapFromCache(
channelMap: ChannelSyncSelectType,
fileChannelMap: Map<
string,
{ id: string; membershipType: string; companyId?: string; clientId?: string }
>,
companyMap: Map<string, { id: string }>,
clientMap: Map<string, { id: string }>,
): MapList | null {
const fileChannel = fileChannelMap.get(channelMap.assemblyChannelId)
if (!fileChannel) return null

let fileChannelValue: UserCompanySelectorInputValue[]

if (fileChannel.membershipType === FileChannelMembership.COMPANY) {
if (!fileChannel.companyId) {
console.error('Company id not found')
return null
}

// calculate synced percentage.
const syncedPercentage = this.getSyncedPercentage(
channelMap.status,
channelMap.syncedFilesCount,
channelMap.totalFilesCount,
)

const formattedChannelInfo = {
id: channelMap.id,
fileChannelValue,
dbxRootPath: channelMap.dbxRootPath,
status: channelMap.status,
fileChannelId: fileChannel.id,
lastSyncedAt: channelMap.lastSyncedAt,
syncedPercentage,
const company = companyMap.get(fileChannel.companyId)
if (!company) {
console.error('Company not found in batch response', fileChannel.companyId)
return null
}
logger.info('MapFilesService#formatChannelMap :: Formatted channel map', formattedChannelInfo)

return formattedChannelInfo
} catch (error: unknown) {
if (error instanceof ApiError && error.status === httpStatus.BAD_REQUEST) {
console.info('Soft delete channel map and make it inactive')
await this.deleteChannelMapById(channelMap.id)
fileChannelValue = [{ id: company.id, companyId: company.id, object: 'company' as const }]
} else {
if (!fileChannel.clientId) {
console.error('Client id not found')
return null
}
logger.error('MapFilesService#formatChannelMap :: Error formatting channel map', error)
return null
const client = clientMap.get(fileChannel.clientId)
if (!client) {
console.error('Client not found in batch response', fileChannel.clientId)
return null
}
fileChannelValue = [
{
id: client.id,
companyId: z.string().parse(fileChannel.companyId),
object: 'client' as const,
},
]
}

const syncedPercentage = this.getSyncedPercentage(
channelMap.status,
channelMap.syncedFilesCount,
channelMap.totalFilesCount,
)

return {
id: channelMap.id,
fileChannelValue,
dbxRootPath: channelMap.dbxRootPath,
status: channelMap.status,
fileChannelId: fileChannel.id,
lastSyncedAt: channelMap.lastSyncedAt,
syncedPercentage,
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/features/sync/lib/Sync.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,6 @@ export class SyncService extends AuthenticatedDropboxService {
}

async removeChannelSyncMapping(channelSyncId: string) {
await this.mapFilesService.deleteChannelMapById(channelSyncId)
await this.mapFilesService.deleteChannelMapsByIds([channelSyncId])
}
}
Loading