|
|
|
@ -27,12 +27,13 @@ import {
|
|
|
|
|
} from '@ghostfolio/common/interfaces';
|
|
|
|
|
import { MarketDataPreset } from '@ghostfolio/common/types';
|
|
|
|
|
|
|
|
|
|
import { BadRequestException, Injectable } from '@nestjs/common';
|
|
|
|
|
import { BadRequestException, Injectable, Logger } from '@nestjs/common';
|
|
|
|
|
import {
|
|
|
|
|
AssetClass,
|
|
|
|
|
AssetSubClass,
|
|
|
|
|
DataSource,
|
|
|
|
|
Prisma,
|
|
|
|
|
PrismaClient,
|
|
|
|
|
Property,
|
|
|
|
|
SymbolProfile
|
|
|
|
|
} from '@prisma/client';
|
|
|
|
@ -212,98 +213,113 @@ export class AdminService {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let [assetProfiles, count] = await Promise.all([
|
|
|
|
|
this.prismaService.symbolProfile.findMany({
|
|
|
|
|
orderBy,
|
|
|
|
|
skip,
|
|
|
|
|
take,
|
|
|
|
|
where,
|
|
|
|
|
select: {
|
|
|
|
|
_count: {
|
|
|
|
|
select: { Order: true }
|
|
|
|
|
},
|
|
|
|
|
assetClass: true,
|
|
|
|
|
assetSubClass: true,
|
|
|
|
|
comment: true,
|
|
|
|
|
countries: true,
|
|
|
|
|
currency: true,
|
|
|
|
|
dataSource: true,
|
|
|
|
|
id: true,
|
|
|
|
|
name: true,
|
|
|
|
|
Order: {
|
|
|
|
|
orderBy: [{ date: 'asc' }],
|
|
|
|
|
select: { date: true },
|
|
|
|
|
take: 1
|
|
|
|
|
},
|
|
|
|
|
scraperConfiguration: true,
|
|
|
|
|
sectors: true,
|
|
|
|
|
symbol: true
|
|
|
|
|
}
|
|
|
|
|
}),
|
|
|
|
|
this.prismaService.symbolProfile.count({ where })
|
|
|
|
|
]);
|
|
|
|
|
const extendedPrismaClient = this.getExtendedPrismaClient();
|
|
|
|
|
|
|
|
|
|
let marketData: AdminMarketDataItem[] = assetProfiles.map(
|
|
|
|
|
({
|
|
|
|
|
_count,
|
|
|
|
|
assetClass,
|
|
|
|
|
assetSubClass,
|
|
|
|
|
comment,
|
|
|
|
|
countries,
|
|
|
|
|
currency,
|
|
|
|
|
dataSource,
|
|
|
|
|
id,
|
|
|
|
|
name,
|
|
|
|
|
Order,
|
|
|
|
|
sectors,
|
|
|
|
|
symbol
|
|
|
|
|
}) => {
|
|
|
|
|
const countriesCount = countries ? Object.keys(countries).length : 0;
|
|
|
|
|
const marketDataItemCount =
|
|
|
|
|
marketDataItems.find((marketDataItem) => {
|
|
|
|
|
return (
|
|
|
|
|
marketDataItem.dataSource === dataSource &&
|
|
|
|
|
marketDataItem.symbol === symbol
|
|
|
|
|
);
|
|
|
|
|
})?._count ?? 0;
|
|
|
|
|
const sectorsCount = sectors ? Object.keys(sectors).length : 0;
|
|
|
|
|
try {
|
|
|
|
|
let [assetProfiles, count] = await Promise.all([
|
|
|
|
|
extendedPrismaClient.symbolProfile.findMany({
|
|
|
|
|
orderBy,
|
|
|
|
|
skip,
|
|
|
|
|
take,
|
|
|
|
|
where,
|
|
|
|
|
select: {
|
|
|
|
|
_count: {
|
|
|
|
|
select: { Order: true }
|
|
|
|
|
},
|
|
|
|
|
assetClass: true,
|
|
|
|
|
assetSubClass: true,
|
|
|
|
|
comment: true,
|
|
|
|
|
countries: true,
|
|
|
|
|
currency: true,
|
|
|
|
|
dataSource: true,
|
|
|
|
|
id: true,
|
|
|
|
|
isUsedByUsersWithSubscription: true,
|
|
|
|
|
name: true,
|
|
|
|
|
Order: {
|
|
|
|
|
orderBy: [{ date: 'asc' }],
|
|
|
|
|
select: { date: true },
|
|
|
|
|
take: 1
|
|
|
|
|
},
|
|
|
|
|
scraperConfiguration: true,
|
|
|
|
|
sectors: true,
|
|
|
|
|
symbol: true
|
|
|
|
|
}
|
|
|
|
|
}),
|
|
|
|
|
this.prismaService.symbolProfile.count({ where })
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
assetClass,
|
|
|
|
|
assetSubClass,
|
|
|
|
|
comment,
|
|
|
|
|
currency,
|
|
|
|
|
countriesCount,
|
|
|
|
|
dataSource,
|
|
|
|
|
id,
|
|
|
|
|
name,
|
|
|
|
|
symbol,
|
|
|
|
|
marketDataItemCount,
|
|
|
|
|
sectorsCount,
|
|
|
|
|
activitiesCount: _count.Order,
|
|
|
|
|
date: Order?.[0]?.date
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
let marketData: AdminMarketDataItem[] = await Promise.all(
|
|
|
|
|
assetProfiles.map(
|
|
|
|
|
async ({
|
|
|
|
|
_count,
|
|
|
|
|
assetClass,
|
|
|
|
|
assetSubClass,
|
|
|
|
|
comment,
|
|
|
|
|
countries,
|
|
|
|
|
currency,
|
|
|
|
|
dataSource,
|
|
|
|
|
id,
|
|
|
|
|
isUsedByUsersWithSubscription,
|
|
|
|
|
name,
|
|
|
|
|
Order,
|
|
|
|
|
sectors,
|
|
|
|
|
symbol
|
|
|
|
|
}) => {
|
|
|
|
|
const countriesCount = countries
|
|
|
|
|
? Object.keys(countries).length
|
|
|
|
|
: 0;
|
|
|
|
|
const marketDataItemCount =
|
|
|
|
|
marketDataItems.find((marketDataItem) => {
|
|
|
|
|
return (
|
|
|
|
|
marketDataItem.dataSource === dataSource &&
|
|
|
|
|
marketDataItem.symbol === symbol
|
|
|
|
|
);
|
|
|
|
|
})?._count ?? 0;
|
|
|
|
|
const sectorsCount = sectors ? Object.keys(sectors).length : 0;
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
assetClass,
|
|
|
|
|
assetSubClass,
|
|
|
|
|
comment,
|
|
|
|
|
currency,
|
|
|
|
|
countriesCount,
|
|
|
|
|
dataSource,
|
|
|
|
|
id,
|
|
|
|
|
name,
|
|
|
|
|
symbol,
|
|
|
|
|
marketDataItemCount,
|
|
|
|
|
sectorsCount,
|
|
|
|
|
activitiesCount: _count.Order,
|
|
|
|
|
date: Order?.[0]?.date,
|
|
|
|
|
isUsedByUsersWithSubscription: await isUsedByUsersWithSubscription
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
});
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
count = marketData.length;
|
|
|
|
|
}
|
|
|
|
|
return {
|
|
|
|
|
count,
|
|
|
|
|
marketData
|
|
|
|
|
};
|
|
|
|
|
} finally {
|
|
|
|
|
await extendedPrismaClient.$disconnect();
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
count,
|
|
|
|
|
marketData
|
|
|
|
|
};
|
|
|
|
|
Logger.debug('Disconnect extended prisma client', 'AdminService');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async getMarketDataBySymbol({
|
|
|
|
@ -431,6 +447,52 @@ export class AdminService {
|
|
|
|
|
return response;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private getExtendedPrismaClient() {
|
|
|
|
|
Logger.debug('Connect extended prisma client', 'AdminService');
|
|
|
|
|
|
|
|
|
|
const symbolProfileExtension = Prisma.defineExtension((client) => {
|
|
|
|
|
return client.$extends({
|
|
|
|
|
result: {
|
|
|
|
|
symbolProfile: {
|
|
|
|
|
isUsedByUsersWithSubscription: {
|
|
|
|
|
compute: async ({ id }) => {
|
|
|
|
|
const { _count } =
|
|
|
|
|
await this.prismaService.symbolProfile.findUnique({
|
|
|
|
|
select: {
|
|
|
|
|
_count: {
|
|
|
|
|
select: {
|
|
|
|
|
Order: {
|
|
|
|
|
where: {
|
|
|
|
|
User: {
|
|
|
|
|
Subscription: {
|
|
|
|
|
some: {
|
|
|
|
|
expiresAt: {
|
|
|
|
|
gt: new Date()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
where: {
|
|
|
|
|
id
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return _count.Order > 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return new PrismaClient().$extends(symbolProfileExtension);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async getMarketDataForCurrencies(): Promise<AdminMarketData> {
|
|
|
|
|
const marketDataItems = await this.prismaService.marketData.groupBy({
|
|
|
|
|
_count: true,
|
|
|
|
|