2021-08-22 22:11:05 +02:00
|
|
|
import { SubscriptionService } from '@ghostfolio/api/app/subscription/subscription.service';
|
|
|
|
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
|
2021-08-09 21:11:35 +02:00
|
|
|
import { DataGatheringService } from '@ghostfolio/api/services/data-gathering.service';
|
2021-04-21 20:27:39 +02:00
|
|
|
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service';
|
2021-11-28 19:46:34 +01:00
|
|
|
import { MarketDataService } from '@ghostfolio/api/services/market-data.service';
|
2021-04-21 20:27:39 +02:00
|
|
|
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
2021-12-04 21:05:11 +01:00
|
|
|
import { PropertyService } from '@ghostfolio/api/services/property/property.service';
|
|
|
|
import { PROPERTY_CURRENCIES, baseCurrency } from '@ghostfolio/common/config';
|
2021-11-28 19:46:34 +01:00
|
|
|
import {
|
|
|
|
AdminData,
|
|
|
|
AdminMarketData,
|
2022-01-23 17:02:12 +01:00
|
|
|
AdminMarketDataDetails,
|
|
|
|
AdminMarketDataItem
|
2021-11-28 19:46:34 +01:00
|
|
|
} from '@ghostfolio/common/interfaces';
|
2021-04-13 21:53:58 +02:00
|
|
|
import { Injectable } from '@nestjs/common';
|
2021-12-06 20:51:38 +01:00
|
|
|
import { Property } from '@prisma/client';
|
2021-08-14 11:12:08 +02:00
|
|
|
import { differenceInDays } from 'date-fns';
|
2021-04-13 21:53:58 +02:00
|
|
|
|
|
|
|
@Injectable()
|
|
|
|
export class AdminService {
|
|
|
|
public constructor(
|
2021-08-22 22:11:05 +02:00
|
|
|
private readonly configurationService: ConfigurationService,
|
2021-08-09 21:11:35 +02:00
|
|
|
private readonly dataGatheringService: DataGatheringService,
|
2021-08-07 22:38:07 +02:00
|
|
|
private readonly exchangeRateDataService: ExchangeRateDataService,
|
2021-11-28 19:46:34 +01:00
|
|
|
private readonly marketDataService: MarketDataService,
|
2021-08-22 22:11:05 +02:00
|
|
|
private readonly prismaService: PrismaService,
|
2021-12-04 21:05:11 +01:00
|
|
|
private readonly propertyService: PropertyService,
|
2021-08-22 22:11:05 +02:00
|
|
|
private readonly subscriptionService: SubscriptionService
|
2021-04-13 21:53:58 +02:00
|
|
|
) {}
|
|
|
|
|
|
|
|
public async get(): Promise<AdminData> {
|
|
|
|
return {
|
2021-11-13 11:32:28 +01:00
|
|
|
dataGatheringProgress:
|
|
|
|
await this.dataGatheringService.getDataGatheringProgress(),
|
2021-09-24 21:09:48 +02:00
|
|
|
exchangeRates: this.exchangeRateDataService
|
|
|
|
.getCurrencies()
|
|
|
|
.filter((currency) => {
|
|
|
|
return currency !== baseCurrency;
|
|
|
|
})
|
|
|
|
.map((currency) => {
|
|
|
|
return {
|
|
|
|
label1: baseCurrency,
|
|
|
|
label2: currency,
|
|
|
|
value: this.exchangeRateDataService.toCurrency(
|
|
|
|
1,
|
|
|
|
baseCurrency,
|
|
|
|
currency
|
|
|
|
)
|
|
|
|
};
|
|
|
|
}),
|
2021-04-13 21:53:58 +02:00
|
|
|
lastDataGathering: await this.getLastDataGathering(),
|
2021-12-04 21:05:11 +01:00
|
|
|
settings: await this.propertyService.get(),
|
2021-08-07 22:38:07 +02:00
|
|
|
transactionCount: await this.prismaService.order.count(),
|
|
|
|
userCount: await this.prismaService.user.count(),
|
2021-04-18 20:12:58 +02:00
|
|
|
users: await this.getUsersWithAnalytics()
|
2021-04-13 21:53:58 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2021-11-28 19:46:34 +01:00
|
|
|
public async getMarketData(): Promise<AdminMarketData> {
|
2022-01-23 17:02:12 +01:00
|
|
|
const marketData = await this.prismaService.marketData.groupBy({
|
|
|
|
_count: true,
|
|
|
|
by: ['dataSource', 'symbol']
|
|
|
|
});
|
|
|
|
|
|
|
|
const currencyPairsToGather: AdminMarketDataItem[] =
|
|
|
|
this.exchangeRateDataService
|
|
|
|
.getCurrencyPairs()
|
|
|
|
.map(({ dataSource, symbol }) => {
|
|
|
|
const marketDataItemCount =
|
|
|
|
marketData.find((marketDataItem) => {
|
|
|
|
return (
|
|
|
|
marketDataItem.dataSource === dataSource &&
|
|
|
|
marketDataItem.symbol === symbol
|
|
|
|
);
|
|
|
|
})?._count ?? 0;
|
|
|
|
|
|
|
|
return {
|
|
|
|
dataSource,
|
|
|
|
marketDataItemCount,
|
|
|
|
symbol
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
const symbolProfilesToGather: AdminMarketDataItem[] = (
|
|
|
|
await this.prismaService.symbolProfile.findMany({
|
|
|
|
orderBy: [{ symbol: 'asc' }],
|
|
|
|
select: {
|
|
|
|
_count: {
|
|
|
|
select: { Order: true }
|
|
|
|
},
|
|
|
|
dataSource: true,
|
|
|
|
Order: {
|
|
|
|
orderBy: [{ date: 'asc' }],
|
|
|
|
select: { date: true },
|
|
|
|
take: 1
|
|
|
|
},
|
|
|
|
scraperConfiguration: true,
|
|
|
|
symbol: true
|
|
|
|
}
|
2021-11-28 19:46:34 +01:00
|
|
|
})
|
2022-01-23 17:02:12 +01:00
|
|
|
).map((symbolProfile) => {
|
|
|
|
const marketDataItemCount =
|
|
|
|
marketData.find((marketDataItem) => {
|
|
|
|
return (
|
|
|
|
marketDataItem.dataSource === symbolProfile.dataSource &&
|
|
|
|
marketDataItem.symbol === symbolProfile.symbol
|
|
|
|
);
|
|
|
|
})?._count ?? 0;
|
|
|
|
|
|
|
|
return {
|
|
|
|
marketDataItemCount,
|
|
|
|
activityCount: symbolProfile._count.Order,
|
|
|
|
dataSource: symbolProfile.dataSource,
|
|
|
|
date: symbolProfile.Order?.[0]?.date,
|
|
|
|
symbol: symbolProfile.symbol
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
return {
|
|
|
|
marketData: [...currencyPairsToGather, ...symbolProfilesToGather]
|
2021-11-28 19:46:34 +01:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
public async getMarketDataBySymbol(
|
|
|
|
aSymbol: string
|
|
|
|
): Promise<AdminMarketDataDetails> {
|
|
|
|
return {
|
|
|
|
marketData: await this.marketDataService.marketDataItems({
|
|
|
|
orderBy: {
|
|
|
|
date: 'asc'
|
|
|
|
},
|
|
|
|
where: {
|
|
|
|
symbol: aSymbol
|
|
|
|
}
|
|
|
|
})
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2021-12-04 21:05:11 +01:00
|
|
|
public async putSetting(key: string, value: string) {
|
2021-12-06 20:51:38 +01:00
|
|
|
let response: Property;
|
|
|
|
|
|
|
|
if (value === '') {
|
|
|
|
response = await this.propertyService.delete({ key });
|
|
|
|
} else {
|
|
|
|
response = await this.propertyService.put({ key, value });
|
|
|
|
}
|
2021-12-04 21:05:11 +01:00
|
|
|
|
|
|
|
if (key === PROPERTY_CURRENCIES) {
|
|
|
|
await this.exchangeRateDataService.initialize();
|
|
|
|
await this.dataGatheringService.reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
return response;
|
|
|
|
}
|
|
|
|
|
2021-04-13 21:53:58 +02:00
|
|
|
private async getLastDataGathering() {
|
2021-08-09 21:11:35 +02:00
|
|
|
const lastDataGathering =
|
|
|
|
await this.dataGatheringService.getLastDataGathering();
|
2021-04-13 21:53:58 +02:00
|
|
|
|
2021-08-09 21:11:35 +02:00
|
|
|
if (lastDataGathering) {
|
|
|
|
return lastDataGathering;
|
2021-04-13 21:53:58 +02:00
|
|
|
}
|
|
|
|
|
2021-08-07 22:38:07 +02:00
|
|
|
const dataGatheringInProgress =
|
2021-08-09 21:11:35 +02:00
|
|
|
await this.dataGatheringService.getIsInProgress();
|
2021-04-13 21:53:58 +02:00
|
|
|
|
|
|
|
if (dataGatheringInProgress) {
|
|
|
|
return 'IN_PROGRESS';
|
|
|
|
}
|
|
|
|
|
2021-11-13 11:32:28 +01:00
|
|
|
return undefined;
|
2021-04-13 21:53:58 +02:00
|
|
|
}
|
|
|
|
|
2021-08-14 11:12:08 +02:00
|
|
|
private async getUsersWithAnalytics(): Promise<AdminData['users']> {
|
|
|
|
const usersWithAnalytics = await this.prismaService.user.findMany({
|
2021-04-18 20:12:58 +02:00
|
|
|
orderBy: {
|
|
|
|
Analytics: {
|
|
|
|
updatedAt: 'desc'
|
|
|
|
}
|
|
|
|
},
|
2021-04-13 21:53:58 +02:00
|
|
|
select: {
|
2021-04-18 20:12:58 +02:00
|
|
|
_count: {
|
2021-04-25 21:25:23 +02:00
|
|
|
select: { Account: true, Order: true }
|
2021-04-18 20:12:58 +02:00
|
|
|
},
|
|
|
|
alias: true,
|
|
|
|
Analytics: {
|
2021-04-13 21:53:58 +02:00
|
|
|
select: {
|
2021-04-18 20:12:58 +02:00
|
|
|
activityCount: true,
|
|
|
|
updatedAt: true
|
2021-04-13 21:53:58 +02:00
|
|
|
}
|
2021-04-18 20:12:58 +02:00
|
|
|
},
|
|
|
|
createdAt: true,
|
2021-08-22 22:11:05 +02:00
|
|
|
id: true,
|
|
|
|
Subscription: true
|
2021-04-13 21:53:58 +02:00
|
|
|
},
|
2021-05-22 10:17:12 +02:00
|
|
|
take: 30,
|
2021-04-22 20:55:05 +02:00
|
|
|
where: {
|
|
|
|
NOT: {
|
|
|
|
Analytics: null
|
|
|
|
}
|
|
|
|
}
|
2021-04-13 21:53:58 +02:00
|
|
|
});
|
2021-08-14 11:12:08 +02:00
|
|
|
|
|
|
|
return usersWithAnalytics.map(
|
2021-08-22 22:11:05 +02:00
|
|
|
({ _count, alias, Analytics, createdAt, id, Subscription }) => {
|
2021-08-14 11:12:08 +02:00
|
|
|
const daysSinceRegistration =
|
|
|
|
differenceInDays(new Date(), createdAt) + 1;
|
|
|
|
const engagement = Analytics.activityCount / daysSinceRegistration;
|
|
|
|
|
2021-08-22 22:11:05 +02:00
|
|
|
const subscription = this.configurationService.get(
|
|
|
|
'ENABLE_FEATURE_SUBSCRIPTION'
|
|
|
|
)
|
|
|
|
? this.subscriptionService.getSubscription(Subscription)
|
|
|
|
: undefined;
|
|
|
|
|
2021-08-14 11:12:08 +02:00
|
|
|
return {
|
|
|
|
alias,
|
|
|
|
createdAt,
|
|
|
|
engagement,
|
|
|
|
id,
|
2021-08-22 22:11:05 +02:00
|
|
|
subscription,
|
2021-08-14 11:12:08 +02:00
|
|
|
accountCount: _count.Account || 0,
|
|
|
|
lastActivity: Analytics.updatedAt,
|
|
|
|
transactionCount: _count.Order || 0
|
|
|
|
};
|
|
|
|
}
|
|
|
|
);
|
2021-04-13 21:53:58 +02:00
|
|
|
}
|
|
|
|
}
|