|
|
@ -36,7 +36,6 @@ import { |
|
|
BadRequestException, |
|
|
BadRequestException, |
|
|
HttpException, |
|
|
HttpException, |
|
|
Injectable, |
|
|
Injectable, |
|
|
Logger, |
|
|
|
|
|
NotFoundException |
|
|
NotFoundException |
|
|
} from '@nestjs/common'; |
|
|
} from '@nestjs/common'; |
|
|
import { |
|
|
import { |
|
|
@ -44,7 +43,6 @@ import { |
|
|
AssetSubClass, |
|
|
AssetSubClass, |
|
|
DataSource, |
|
|
DataSource, |
|
|
Prisma, |
|
|
Prisma, |
|
|
PrismaClient, |
|
|
|
|
|
Property, |
|
|
Property, |
|
|
SymbolProfile |
|
|
SymbolProfile |
|
|
} from '@prisma/client'; |
|
|
} from '@prisma/client'; |
|
|
@ -280,186 +278,178 @@ export class AdminService { |
|
|
|
|
|
|
|
|
const extendedPrismaClient = this.getExtendedPrismaClient(); |
|
|
const extendedPrismaClient = this.getExtendedPrismaClient(); |
|
|
|
|
|
|
|
|
try { |
|
|
const symbolProfileResult = await Promise.all([ |
|
|
const symbolProfileResult = await Promise.all([ |
|
|
extendedPrismaClient.symbolProfile.findMany({ |
|
|
extendedPrismaClient.symbolProfile.findMany({ |
|
|
skip, |
|
|
skip, |
|
|
take, |
|
|
take, |
|
|
where, |
|
|
where, |
|
|
orderBy: [...orderBy, { id: sortDirection }], |
|
|
orderBy: [...orderBy, { id: sortDirection }], |
|
|
|
|
|
select: { |
|
|
|
|
|
_count: { |
|
|
|
|
|
select: { |
|
|
|
|
|
activities: true, |
|
|
|
|
|
watchedBy: true |
|
|
|
|
|
} |
|
|
|
|
|
}, |
|
|
|
|
|
activities: { |
|
|
|
|
|
orderBy: [{ date: 'asc' }], |
|
|
|
|
|
select: { date: true }, |
|
|
|
|
|
take: 1 |
|
|
|
|
|
}, |
|
|
|
|
|
assetClass: true, |
|
|
|
|
|
assetSubClass: true, |
|
|
|
|
|
comment: true, |
|
|
|
|
|
countries: true, |
|
|
|
|
|
currency: true, |
|
|
|
|
|
dataSource: true, |
|
|
|
|
|
id: true, |
|
|
|
|
|
isActive: true, |
|
|
|
|
|
isUsedByUsersWithSubscription: true, |
|
|
|
|
|
name: true, |
|
|
|
|
|
scraperConfiguration: true, |
|
|
|
|
|
sectors: true, |
|
|
|
|
|
symbol: true, |
|
|
|
|
|
SymbolProfileOverrides: true |
|
|
|
|
|
} |
|
|
|
|
|
}), |
|
|
|
|
|
this.prismaService.symbolProfile.count({ where }) |
|
|
|
|
|
]); |
|
|
|
|
|
const assetProfiles = symbolProfileResult[0]; |
|
|
|
|
|
let count = symbolProfileResult[1]; |
|
|
|
|
|
|
|
|
|
|
|
const lastMarketPrices = await this.prismaService.marketData.findMany({ |
|
|
|
|
|
distinct: ['dataSource', 'symbol'], |
|
|
|
|
|
orderBy: { date: 'desc' }, |
|
|
|
|
|
select: { |
|
|
select: { |
|
|
|
|
|
_count: { |
|
|
|
|
|
select: { |
|
|
|
|
|
activities: true, |
|
|
|
|
|
watchedBy: true |
|
|
|
|
|
} |
|
|
|
|
|
}, |
|
|
|
|
|
activities: { |
|
|
|
|
|
orderBy: [{ date: 'asc' }], |
|
|
|
|
|
select: { date: true }, |
|
|
|
|
|
take: 1 |
|
|
|
|
|
}, |
|
|
|
|
|
assetClass: true, |
|
|
|
|
|
assetSubClass: true, |
|
|
|
|
|
comment: true, |
|
|
|
|
|
countries: true, |
|
|
|
|
|
currency: true, |
|
|
dataSource: true, |
|
|
dataSource: true, |
|
|
marketPrice: true, |
|
|
id: true, |
|
|
symbol: true |
|
|
isActive: true, |
|
|
|
|
|
isUsedByUsersWithSubscription: true, |
|
|
|
|
|
name: true, |
|
|
|
|
|
scraperConfiguration: true, |
|
|
|
|
|
sectors: true, |
|
|
|
|
|
symbol: true, |
|
|
|
|
|
SymbolProfileOverrides: true |
|
|
|
|
|
} |
|
|
|
|
|
}), |
|
|
|
|
|
this.prismaService.symbolProfile.count({ where }) |
|
|
|
|
|
]); |
|
|
|
|
|
const assetProfiles = symbolProfileResult[0]; |
|
|
|
|
|
let count = symbolProfileResult[1]; |
|
|
|
|
|
|
|
|
|
|
|
const lastMarketPrices = await this.prismaService.marketData.findMany({ |
|
|
|
|
|
distinct: ['dataSource', 'symbol'], |
|
|
|
|
|
orderBy: { date: 'desc' }, |
|
|
|
|
|
select: { |
|
|
|
|
|
dataSource: true, |
|
|
|
|
|
marketPrice: true, |
|
|
|
|
|
symbol: true |
|
|
|
|
|
}, |
|
|
|
|
|
where: { |
|
|
|
|
|
dataSource: { |
|
|
|
|
|
in: assetProfiles.map(({ dataSource }) => { |
|
|
|
|
|
return dataSource; |
|
|
|
|
|
}) |
|
|
}, |
|
|
}, |
|
|
where: { |
|
|
symbol: { |
|
|
dataSource: { |
|
|
in: assetProfiles.map(({ symbol }) => { |
|
|
in: assetProfiles.map(({ dataSource }) => { |
|
|
return symbol; |
|
|
return dataSource; |
|
|
}) |
|
|
}) |
|
|
|
|
|
}, |
|
|
|
|
|
symbol: { |
|
|
|
|
|
in: assetProfiles.map(({ symbol }) => { |
|
|
|
|
|
return symbol; |
|
|
|
|
|
}) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
const lastMarketPriceMap = new Map<string, number>(); |
|
|
const lastMarketPriceMap = new Map<string, number>(); |
|
|
|
|
|
|
|
|
for (const { dataSource, marketPrice, symbol } of lastMarketPrices) { |
|
|
for (const { dataSource, marketPrice, symbol } of lastMarketPrices) { |
|
|
lastMarketPriceMap.set( |
|
|
lastMarketPriceMap.set( |
|
|
getAssetProfileIdentifier({ dataSource, symbol }), |
|
|
getAssetProfileIdentifier({ dataSource, symbol }), |
|
|
marketPrice |
|
|
marketPrice |
|
|
); |
|
|
); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
let marketData: AdminMarketDataItem[] = await Promise.all( |
|
|
|
|
|
assetProfiles.map( |
|
|
|
|
|
async ({ |
|
|
|
|
|
_count, |
|
|
|
|
|
activities, |
|
|
|
|
|
assetClass, |
|
|
|
|
|
assetSubClass, |
|
|
|
|
|
comment, |
|
|
|
|
|
countries, |
|
|
|
|
|
currency, |
|
|
|
|
|
dataSource, |
|
|
|
|
|
id, |
|
|
|
|
|
isActive, |
|
|
|
|
|
isUsedByUsersWithSubscription, |
|
|
|
|
|
name, |
|
|
|
|
|
sectors, |
|
|
|
|
|
symbol, |
|
|
|
|
|
SymbolProfileOverrides |
|
|
|
|
|
}) => { |
|
|
|
|
|
let countriesCount = countries ? Object.keys(countries).length : 0; |
|
|
|
|
|
|
|
|
|
|
|
const lastMarketPrice = lastMarketPriceMap.get( |
|
|
|
|
|
getAssetProfileIdentifier({ dataSource, symbol }) |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
const marketDataItemCount = |
|
|
|
|
|
marketDataItems.find((marketDataItem) => { |
|
|
|
|
|
return ( |
|
|
|
|
|
marketDataItem.dataSource === dataSource && |
|
|
|
|
|
marketDataItem.symbol === symbol |
|
|
|
|
|
); |
|
|
|
|
|
})?._count ?? 0; |
|
|
|
|
|
|
|
|
|
|
|
let sectorsCount = sectors ? Object.keys(sectors).length : 0; |
|
|
|
|
|
|
|
|
|
|
|
if (SymbolProfileOverrides) { |
|
|
|
|
|
assetClass = SymbolProfileOverrides.assetClass ?? assetClass; |
|
|
|
|
|
assetSubClass = |
|
|
|
|
|
SymbolProfileOverrides.assetSubClass ?? assetSubClass; |
|
|
|
|
|
|
|
|
|
|
|
if ( |
|
|
|
|
|
(SymbolProfileOverrides.countries as unknown as Prisma.JsonArray) |
|
|
|
|
|
?.length > 0 |
|
|
|
|
|
) { |
|
|
|
|
|
countriesCount = ( |
|
|
|
|
|
SymbolProfileOverrides.countries as unknown as Prisma.JsonArray |
|
|
|
|
|
).length; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
let marketData: AdminMarketDataItem[] = await Promise.all( |
|
|
name = SymbolProfileOverrides.name ?? name; |
|
|
assetProfiles.map( |
|
|
|
|
|
async ({ |
|
|
if ( |
|
|
_count, |
|
|
(SymbolProfileOverrides.sectors as unknown as Sector[])?.length > |
|
|
activities, |
|
|
0 |
|
|
|
|
|
) { |
|
|
|
|
|
sectorsCount = ( |
|
|
|
|
|
SymbolProfileOverrides.sectors as unknown as Prisma.JsonArray |
|
|
|
|
|
).length; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return { |
|
|
assetClass, |
|
|
assetClass, |
|
|
assetSubClass, |
|
|
assetSubClass, |
|
|
comment, |
|
|
comment, |
|
|
countries, |
|
|
countriesCount, |
|
|
currency, |
|
|
currency, |
|
|
dataSource, |
|
|
dataSource, |
|
|
id, |
|
|
id, |
|
|
isActive, |
|
|
isActive, |
|
|
isUsedByUsersWithSubscription, |
|
|
lastMarketPrice, |
|
|
|
|
|
marketDataItemCount, |
|
|
name, |
|
|
name, |
|
|
sectors, |
|
|
sectorsCount, |
|
|
symbol, |
|
|
symbol, |
|
|
SymbolProfileOverrides |
|
|
activitiesCount: _count.activities, |
|
|
}) => { |
|
|
date: activities?.[0]?.date, |
|
|
let countriesCount = countries ? Object.keys(countries).length : 0; |
|
|
isUsedByUsersWithSubscription: await isUsedByUsersWithSubscription, |
|
|
|
|
|
watchedByCount: _count.watchedBy |
|
|
const lastMarketPrice = lastMarketPriceMap.get( |
|
|
}; |
|
|
getAssetProfileIdentifier({ dataSource, symbol }) |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
const marketDataItemCount = |
|
|
|
|
|
marketDataItems.find((marketDataItem) => { |
|
|
|
|
|
return ( |
|
|
|
|
|
marketDataItem.dataSource === dataSource && |
|
|
|
|
|
marketDataItem.symbol === symbol |
|
|
|
|
|
); |
|
|
|
|
|
})?._count ?? 0; |
|
|
|
|
|
|
|
|
|
|
|
let sectorsCount = sectors ? Object.keys(sectors).length : 0; |
|
|
|
|
|
|
|
|
|
|
|
if (SymbolProfileOverrides) { |
|
|
|
|
|
assetClass = SymbolProfileOverrides.assetClass ?? assetClass; |
|
|
|
|
|
assetSubClass = |
|
|
|
|
|
SymbolProfileOverrides.assetSubClass ?? assetSubClass; |
|
|
|
|
|
|
|
|
|
|
|
if ( |
|
|
|
|
|
( |
|
|
|
|
|
SymbolProfileOverrides.countries as unknown as Prisma.JsonArray |
|
|
|
|
|
)?.length > 0 |
|
|
|
|
|
) { |
|
|
|
|
|
countriesCount = ( |
|
|
|
|
|
SymbolProfileOverrides.countries as unknown as Prisma.JsonArray |
|
|
|
|
|
).length; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
name = SymbolProfileOverrides.name ?? name; |
|
|
|
|
|
|
|
|
|
|
|
if ( |
|
|
|
|
|
(SymbolProfileOverrides.sectors as unknown as Sector[]) |
|
|
|
|
|
?.length > 0 |
|
|
|
|
|
) { |
|
|
|
|
|
sectorsCount = ( |
|
|
|
|
|
SymbolProfileOverrides.sectors as unknown as Prisma.JsonArray |
|
|
|
|
|
).length; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return { |
|
|
|
|
|
assetClass, |
|
|
|
|
|
assetSubClass, |
|
|
|
|
|
comment, |
|
|
|
|
|
currency, |
|
|
|
|
|
countriesCount, |
|
|
|
|
|
dataSource, |
|
|
|
|
|
id, |
|
|
|
|
|
isActive, |
|
|
|
|
|
lastMarketPrice, |
|
|
|
|
|
name, |
|
|
|
|
|
symbol, |
|
|
|
|
|
marketDataItemCount, |
|
|
|
|
|
sectorsCount, |
|
|
|
|
|
activitiesCount: _count.activities, |
|
|
|
|
|
date: activities?.[0]?.date, |
|
|
|
|
|
isUsedByUsersWithSubscription: |
|
|
|
|
|
await isUsedByUsersWithSubscription, |
|
|
|
|
|
watchedByCount: _count.watchedBy |
|
|
|
|
|
}; |
|
|
|
|
|
} |
|
|
|
|
|
) |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
if (presetId) { |
|
|
|
|
|
if (presetId === 'ETF_WITHOUT_COUNTRIES') { |
|
|
|
|
|
marketData = marketData.filter(({ countriesCount }) => { |
|
|
|
|
|
return countriesCount === 0; |
|
|
|
|
|
}); |
|
|
|
|
|
} else if (presetId === 'ETF_WITHOUT_SECTORS') { |
|
|
|
|
|
marketData = marketData.filter(({ sectorsCount }) => { |
|
|
|
|
|
return sectorsCount === 0; |
|
|
|
|
|
}); |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
) |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
count = marketData.length; |
|
|
if (presetId) { |
|
|
|
|
|
if (presetId === 'ETF_WITHOUT_COUNTRIES') { |
|
|
|
|
|
marketData = marketData.filter(({ countriesCount }) => { |
|
|
|
|
|
return countriesCount === 0; |
|
|
|
|
|
}); |
|
|
|
|
|
} else if (presetId === 'ETF_WITHOUT_SECTORS') { |
|
|
|
|
|
marketData = marketData.filter(({ sectorsCount }) => { |
|
|
|
|
|
return sectorsCount === 0; |
|
|
|
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
return { |
|
|
count = marketData.length; |
|
|
count, |
|
|
|
|
|
marketData |
|
|
|
|
|
}; |
|
|
|
|
|
} finally { |
|
|
|
|
|
await extendedPrismaClient.$disconnect(); |
|
|
|
|
|
|
|
|
|
|
|
Logger.debug('Disconnect extended prisma client', 'AdminService'); |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return { |
|
|
|
|
|
count, |
|
|
|
|
|
marketData |
|
|
|
|
|
}; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
public async getMarketDataBySymbol({ |
|
|
public async getMarketDataBySymbol({ |
|
|
@ -704,8 +694,6 @@ export class AdminService { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private getExtendedPrismaClient() { |
|
|
private getExtendedPrismaClient() { |
|
|
Logger.debug('Connect extended prisma client', 'AdminService'); |
|
|
|
|
|
|
|
|
|
|
|
const symbolProfileExtension = Prisma.defineExtension((client) => { |
|
|
const symbolProfileExtension = Prisma.defineExtension((client) => { |
|
|
return client.$extends({ |
|
|
return client.$extends({ |
|
|
result: { |
|
|
result: { |
|
|
@ -746,7 +734,7 @@ export class AdminService { |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
return new PrismaClient().$extends(symbolProfileExtension); |
|
|
return this.prismaService.$extends(symbolProfileExtension); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private async getMarketDataForCurrencies(): Promise<AdminMarketData> { |
|
|
private async getMarketDataForCurrencies(): Promise<AdminMarketData> { |
|
|
|