Compare commits
19 Commits
Author | SHA1 | Date | |
---|---|---|---|
f781eb207c | |||
7b6893b5ed | |||
07799573cb | |||
9cdef6a7cb | |||
0d897bc461 | |||
e4908b51aa | |||
718b0de0a7 | |||
99655604d9 | |||
b602e7690b | |||
7745dafe48 | |||
50184284e1 | |||
f46533107d | |||
c216ab1d76 | |||
86acbf06f4 | |||
3de7d3f60e | |||
63ed227f3f | |||
5bb20f6d5f | |||
b3e58d182a | |||
93d6746739 |
52
CHANGELOG.md
52
CHANGELOG.md
@ -5,6 +5,58 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## 1.125.0 - 12.03.2022
|
||||
|
||||
### Added
|
||||
|
||||
- Added support for an emergency fund
|
||||
- Added the contexts to the logger commands
|
||||
|
||||
### Changed
|
||||
|
||||
- Upgraded `Nx` from version `13.8.1` to `13.8.5`
|
||||
|
||||
## 1.124.0 - 06.03.2022
|
||||
|
||||
### Added
|
||||
|
||||
- Added support for setting a duration in the coupon system
|
||||
|
||||
### Changed
|
||||
|
||||
- Upgraded `ngx-skeleton-loader` from version `2.9.1` to `5.0.0`
|
||||
- Upgraded `prisma` from version `3.9.1` to `3.10.0`
|
||||
- Upgraded `yahoo-finance2` from version `2.1.9` to `2.2.0`
|
||||
|
||||
## 1.123.0 - 05.03.2022
|
||||
|
||||
### Added
|
||||
|
||||
- Included data provider errors in the API response
|
||||
|
||||
### Changed
|
||||
|
||||
- Removed the redundant attributes (`currency`, `dataSource`, `symbol`) of the activity model
|
||||
- Removed the prefix for symbols with the data source `GHOSTFOLIO`
|
||||
|
||||
### Fixed
|
||||
|
||||
- Improved the account calculations
|
||||
|
||||
### Todo
|
||||
|
||||
- Apply data migration (`yarn database:migrate`)
|
||||
|
||||
## 1.122.0 - 01.03.2022
|
||||
|
||||
### Added
|
||||
|
||||
- Added support for click in the portfolio proportion chart component
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed an issue with undefined currencies after creating an activity
|
||||
|
||||
## 1.121.0 - 27.02.2022
|
||||
|
||||
### Added
|
||||
|
@ -9,7 +9,7 @@
|
||||
"schematics": {},
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@nrwl/node:build",
|
||||
"builder": "@nrwl/node:webpack",
|
||||
"options": {
|
||||
"outputPath": "dist/apps/api",
|
||||
"main": "apps/api/src/main.ts",
|
||||
@ -33,7 +33,7 @@
|
||||
"outputs": ["{options.outputPath}"]
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@nrwl/node:execute",
|
||||
"builder": "@nrwl/node:node",
|
||||
"options": {
|
||||
"buildTarget": "api:build"
|
||||
}
|
||||
|
@ -101,16 +101,18 @@ export class AccountController {
|
||||
) {
|
||||
accountsWithAggregations = {
|
||||
...nullifyValuesInObject(accountsWithAggregations, [
|
||||
'totalBalance',
|
||||
'totalValue'
|
||||
'totalBalanceInBaseCurrency',
|
||||
'totalValueInBaseCurrency'
|
||||
]),
|
||||
accounts: nullifyValuesInObjects(accountsWithAggregations.accounts, [
|
||||
'balance',
|
||||
'balanceInBaseCurrency',
|
||||
'convertedBalance',
|
||||
'fee',
|
||||
'quantity',
|
||||
'unitPrice',
|
||||
'value'
|
||||
'value',
|
||||
'valueInBaseCurrency'
|
||||
])
|
||||
};
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-
|
||||
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Account, Order, Platform, Prisma } from '@prisma/client';
|
||||
import Big from 'big.js';
|
||||
|
||||
import { CashDetails } from './interfaces/cash-details.interface';
|
||||
|
||||
@ -105,21 +106,26 @@ export class AccountService {
|
||||
aUserId: string,
|
||||
aCurrency: string
|
||||
): Promise<CashDetails> {
|
||||
let totalCashBalance = 0;
|
||||
let totalCashBalanceInBaseCurrency = new Big(0);
|
||||
|
||||
const accounts = await this.accounts({
|
||||
where: { userId: aUserId }
|
||||
});
|
||||
|
||||
accounts.forEach((account) => {
|
||||
totalCashBalance += this.exchangeRateDataService.toCurrency(
|
||||
account.balance,
|
||||
account.currency,
|
||||
aCurrency
|
||||
for (const account of accounts) {
|
||||
totalCashBalanceInBaseCurrency = totalCashBalanceInBaseCurrency.plus(
|
||||
this.exchangeRateDataService.toCurrency(
|
||||
account.balance,
|
||||
account.currency,
|
||||
aCurrency
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return { accounts, balance: totalCashBalance };
|
||||
return {
|
||||
accounts,
|
||||
balanceInBaseCurrency: totalCashBalanceInBaseCurrency.toNumber()
|
||||
};
|
||||
}
|
||||
|
||||
public async updateAccount(
|
||||
|
@ -2,5 +2,5 @@ import { Account } from '@prisma/client';
|
||||
|
||||
export interface CashDetails {
|
||||
accounts: Account[];
|
||||
balance: number;
|
||||
balanceInBaseCurrency: number;
|
||||
}
|
||||
|
@ -11,7 +11,8 @@ import {
|
||||
AdminData,
|
||||
AdminMarketData,
|
||||
AdminMarketDataDetails,
|
||||
AdminMarketDataItem
|
||||
AdminMarketDataItem,
|
||||
UniqueAsset
|
||||
} from '@ghostfolio/common/interfaces';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { DataSource, Property } from '@prisma/client';
|
||||
@ -30,13 +31,7 @@ export class AdminService {
|
||||
private readonly symbolProfileService: SymbolProfileService
|
||||
) {}
|
||||
|
||||
public async deleteProfileData({
|
||||
dataSource,
|
||||
symbol
|
||||
}: {
|
||||
dataSource: DataSource;
|
||||
symbol: string;
|
||||
}) {
|
||||
public async deleteProfileData({ dataSource, symbol }: UniqueAsset) {
|
||||
await this.marketDataService.deleteMany({ dataSource, symbol });
|
||||
await this.symbolProfileService.delete({ dataSource, symbol });
|
||||
}
|
||||
@ -137,10 +132,7 @@ export class AdminService {
|
||||
public async getMarketDataBySymbol({
|
||||
dataSource,
|
||||
symbol
|
||||
}: {
|
||||
dataSource: DataSource;
|
||||
symbol: string;
|
||||
}): Promise<AdminMarketDataDetails> {
|
||||
}: UniqueAsset): Promise<AdminMarketDataDetails> {
|
||||
return {
|
||||
marketData: await this.marketDataService.marketDataItems({
|
||||
orderBy: {
|
||||
|
@ -42,7 +42,7 @@ export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
|
||||
|
||||
done(null, user);
|
||||
} catch (error) {
|
||||
Logger.error(error);
|
||||
Logger.error(error, 'GoogleStrategy');
|
||||
done(error, false);
|
||||
}
|
||||
}
|
||||
|
@ -95,7 +95,7 @@ export class WebAuthService {
|
||||
};
|
||||
verification = await verifyRegistrationResponse(opts);
|
||||
} catch (error) {
|
||||
Logger.error(error);
|
||||
Logger.error(error, 'WebAuthService');
|
||||
throw new InternalServerErrorException(error.message);
|
||||
}
|
||||
|
||||
@ -193,7 +193,7 @@ export class WebAuthService {
|
||||
};
|
||||
verification = verifyAuthenticationResponse(opts);
|
||||
} catch (error) {
|
||||
Logger.error(error);
|
||||
Logger.error(error, 'WebAuthService');
|
||||
throw new InternalServerErrorException({ error: error.message });
|
||||
}
|
||||
|
||||
|
@ -18,8 +18,6 @@ export class ExportService {
|
||||
orderBy: { date: 'desc' },
|
||||
select: {
|
||||
accountId: true,
|
||||
currency: true,
|
||||
dataSource: true,
|
||||
date: true,
|
||||
fee: true,
|
||||
id: true,
|
||||
@ -42,7 +40,6 @@ export class ExportService {
|
||||
orders: orders.map(
|
||||
({
|
||||
accountId,
|
||||
currency,
|
||||
date,
|
||||
fee,
|
||||
quantity,
|
||||
@ -52,12 +49,12 @@ export class ExportService {
|
||||
}) => {
|
||||
return {
|
||||
accountId,
|
||||
currency,
|
||||
date,
|
||||
fee,
|
||||
quantity,
|
||||
type,
|
||||
unitPrice,
|
||||
currency: SymbolProfile.currency,
|
||||
dataSource: SymbolProfile.dataSource,
|
||||
symbol: type === 'ITEM' ? SymbolProfile.name : SymbolProfile.symbol
|
||||
};
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto';
|
||||
import { Order } from '@prisma/client';
|
||||
import { Type } from 'class-transformer';
|
||||
import { IsArray, ValidateNested } from 'class-validator';
|
||||
|
||||
@ -7,5 +6,5 @@ export class ImportDataDto {
|
||||
@IsArray()
|
||||
@Type(() => CreateOrderDto)
|
||||
@ValidateNested({ each: true })
|
||||
orders: Order[];
|
||||
orders: CreateOrderDto[];
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ export class ImportController {
|
||||
userId: this.request.user.id
|
||||
});
|
||||
} catch (error) {
|
||||
Logger.error(error);
|
||||
Logger.error(error, ImportController);
|
||||
|
||||
throw new HttpException(
|
||||
{
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { AccountService } from '@ghostfolio/api/app/account/account.service';
|
||||
import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto';
|
||||
import { OrderService } from '@ghostfolio/api/app/order/order.service';
|
||||
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
|
||||
import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Order } from '@prisma/client';
|
||||
import { isSameDay, parseISO } from 'date-fns';
|
||||
|
||||
@Injectable()
|
||||
@ -19,7 +19,7 @@ export class ImportService {
|
||||
orders,
|
||||
userId
|
||||
}: {
|
||||
orders: Partial<Order>[];
|
||||
orders: Partial<CreateOrderDto>[];
|
||||
userId: string;
|
||||
}): Promise<void> {
|
||||
for (const order of orders) {
|
||||
@ -52,11 +52,8 @@ export class ImportService {
|
||||
unitPrice
|
||||
} of orders) {
|
||||
await this.orderService.createOrder({
|
||||
currency,
|
||||
dataSource,
|
||||
fee,
|
||||
quantity,
|
||||
symbol,
|
||||
type,
|
||||
unitPrice,
|
||||
userId,
|
||||
@ -65,6 +62,7 @@ export class ImportService {
|
||||
SymbolProfile: {
|
||||
connectOrCreate: {
|
||||
create: {
|
||||
currency,
|
||||
dataSource,
|
||||
symbol
|
||||
},
|
||||
@ -85,7 +83,7 @@ export class ImportService {
|
||||
orders,
|
||||
userId
|
||||
}: {
|
||||
orders: Partial<Order>[];
|
||||
orders: Partial<CreateOrderDto>[];
|
||||
userId: string;
|
||||
}) {
|
||||
if (
|
||||
@ -99,6 +97,7 @@ export class ImportService {
|
||||
}
|
||||
|
||||
const existingOrders = await this.orderService.orders({
|
||||
include: { SymbolProfile: true },
|
||||
orderBy: { date: 'desc' },
|
||||
where: { userId }
|
||||
});
|
||||
@ -109,12 +108,12 @@ export class ImportService {
|
||||
] of orders.entries()) {
|
||||
const duplicateOrder = existingOrders.find((order) => {
|
||||
return (
|
||||
order.currency === currency &&
|
||||
order.dataSource === dataSource &&
|
||||
order.SymbolProfile.currency === currency &&
|
||||
order.SymbolProfile.dataSource === dataSource &&
|
||||
isSameDay(order.date, parseISO(<string>(<unknown>date))) &&
|
||||
order.fee === fee &&
|
||||
order.quantity === quantity &&
|
||||
order.symbol === symbol &&
|
||||
order.SymbolProfile.symbol === symbol &&
|
||||
order.type === type &&
|
||||
order.unitPrice === unitPrice
|
||||
);
|
||||
|
@ -144,7 +144,7 @@ export class InfoService {
|
||||
const contributors = await get();
|
||||
return contributors?.length;
|
||||
} catch (error) {
|
||||
Logger.error(error);
|
||||
Logger.error(error, 'InfoService');
|
||||
|
||||
return undefined;
|
||||
}
|
||||
@ -165,7 +165,7 @@ export class InfoService {
|
||||
const { stargazers_count } = await get();
|
||||
return stargazers_count;
|
||||
} catch (error) {
|
||||
Logger.error(error);
|
||||
Logger.error(error, 'InfoService');
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
@ -114,6 +114,7 @@ export class OrderController {
|
||||
SymbolProfile: {
|
||||
connectOrCreate: {
|
||||
create: {
|
||||
currency: data.currency,
|
||||
dataSource: data.dataSource,
|
||||
symbol: data.symbol
|
||||
},
|
||||
@ -171,6 +172,14 @@ export class OrderController {
|
||||
id_userId: { id: accountId, userId: this.request.user.id }
|
||||
}
|
||||
},
|
||||
SymbolProfile: {
|
||||
connect: {
|
||||
dataSource_symbol: {
|
||||
dataSource: data.dataSource,
|
||||
symbol: data.symbol
|
||||
}
|
||||
}
|
||||
},
|
||||
User: { connect: { id: this.request.user.id } }
|
||||
},
|
||||
where: {
|
||||
|
@ -53,7 +53,13 @@ export class OrderService {
|
||||
}
|
||||
|
||||
public async createOrder(
|
||||
data: Prisma.OrderCreateInput & { accountId?: string; userId: string }
|
||||
data: Prisma.OrderCreateInput & {
|
||||
accountId?: string;
|
||||
currency?: string;
|
||||
dataSource?: DataSource;
|
||||
symbol?: string;
|
||||
userId: string;
|
||||
}
|
||||
): Promise<Order> {
|
||||
const defaultAccount = (
|
||||
await this.accountService.getAccounts(data.userId)
|
||||
@ -71,15 +77,13 @@ export class OrderService {
|
||||
};
|
||||
|
||||
if (data.type === 'ITEM') {
|
||||
const currency = data.currency;
|
||||
const currency = data.SymbolProfile.connectOrCreate.create.currency;
|
||||
const dataSource: DataSource = 'MANUAL';
|
||||
const id = uuidv4();
|
||||
const name = data.SymbolProfile.connectOrCreate.create.symbol;
|
||||
|
||||
Account = undefined;
|
||||
data.dataSource = dataSource;
|
||||
data.id = id;
|
||||
data.symbol = null;
|
||||
data.SymbolProfile.connectOrCreate.create.currency = currency;
|
||||
data.SymbolProfile.connectOrCreate.create.dataSource = dataSource;
|
||||
data.SymbolProfile.connectOrCreate.create.name = name;
|
||||
@ -93,29 +97,32 @@ export class OrderService {
|
||||
data.SymbolProfile.connectOrCreate.create.symbol.toUpperCase();
|
||||
}
|
||||
|
||||
await this.dataGatheringService.gatherProfileData([
|
||||
{
|
||||
dataSource: data.SymbolProfile.connectOrCreate.create.dataSource,
|
||||
symbol: data.SymbolProfile.connectOrCreate.create.symbol
|
||||
}
|
||||
]);
|
||||
|
||||
const isDraft = isAfter(data.date as Date, endOfToday());
|
||||
|
||||
if (!isDraft) {
|
||||
// Gather symbol data of order in the background, if not draft
|
||||
this.dataGatheringService.gatherSymbols([
|
||||
{
|
||||
dataSource: data.dataSource,
|
||||
dataSource: data.SymbolProfile.connectOrCreate.create.dataSource,
|
||||
date: <Date>data.date,
|
||||
symbol: data.SymbolProfile.connectOrCreate.create.symbol
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
this.dataGatheringService.gatherProfileData([
|
||||
{
|
||||
dataSource: data.dataSource,
|
||||
symbol: data.SymbolProfile.connectOrCreate.create.symbol
|
||||
}
|
||||
]);
|
||||
|
||||
await this.cacheService.flush();
|
||||
|
||||
delete data.accountId;
|
||||
delete data.currency;
|
||||
delete data.dataSource;
|
||||
delete data.symbol;
|
||||
delete data.userId;
|
||||
|
||||
const orderData: Prisma.OrderCreateInput = data;
|
||||
@ -193,50 +200,60 @@ export class OrderService {
|
||||
value,
|
||||
feeInBaseCurrency: this.exchangeRateDataService.toCurrency(
|
||||
order.fee,
|
||||
order.currency,
|
||||
order.SymbolProfile.currency,
|
||||
userCurrency
|
||||
),
|
||||
valueInBaseCurrency: this.exchangeRateDataService.toCurrency(
|
||||
value,
|
||||
order.currency,
|
||||
order.SymbolProfile.currency,
|
||||
userCurrency
|
||||
)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
public async updateOrder(params: {
|
||||
public async updateOrder({
|
||||
data,
|
||||
where
|
||||
}: {
|
||||
data: Prisma.OrderUpdateInput & {
|
||||
currency?: string;
|
||||
dataSource?: DataSource;
|
||||
symbol?: string;
|
||||
};
|
||||
where: Prisma.OrderWhereUniqueInput;
|
||||
data: Prisma.OrderUpdateInput;
|
||||
}): Promise<Order> {
|
||||
const { data, where } = params;
|
||||
|
||||
if (data.Account.connect.id_userId.id === null) {
|
||||
delete data.Account;
|
||||
}
|
||||
|
||||
let isDraft = false;
|
||||
|
||||
if (data.type === 'ITEM') {
|
||||
const name = data.symbol;
|
||||
const name = data.SymbolProfile.connect.dataSource_symbol.symbol;
|
||||
|
||||
data.symbol = null;
|
||||
data.SymbolProfile = { update: { name } };
|
||||
}
|
||||
} else {
|
||||
isDraft = isAfter(data.date as Date, endOfToday());
|
||||
|
||||
const isDraft = isAfter(data.date as Date, endOfToday());
|
||||
|
||||
if (!isDraft) {
|
||||
// Gather symbol data of order in the background, if not draft
|
||||
this.dataGatheringService.gatherSymbols([
|
||||
{
|
||||
dataSource: <DataSource>data.dataSource,
|
||||
date: <Date>data.date,
|
||||
symbol: <string>data.symbol
|
||||
}
|
||||
]);
|
||||
if (!isDraft) {
|
||||
// Gather symbol data of order in the background, if not draft
|
||||
this.dataGatheringService.gatherSymbols([
|
||||
{
|
||||
dataSource: data.SymbolProfile.connect.dataSource_symbol.dataSource,
|
||||
date: <Date>data.date,
|
||||
symbol: data.SymbolProfile.connect.dataSource_symbol.symbol
|
||||
}
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
await this.cacheService.flush();
|
||||
|
||||
delete data.currency;
|
||||
delete data.dataSource;
|
||||
delete data.symbol;
|
||||
|
||||
return this.prismaService.order.update({
|
||||
data: {
|
||||
...data,
|
||||
|
@ -1,8 +1,7 @@
|
||||
import { TimelinePosition } from '@ghostfolio/common/interfaces';
|
||||
import { ResponseError, TimelinePosition } from '@ghostfolio/common/interfaces';
|
||||
import Big from 'big.js';
|
||||
|
||||
export interface CurrentPositions {
|
||||
hasErrors: boolean;
|
||||
export interface CurrentPositions extends ResponseError {
|
||||
positions: TimelinePosition[];
|
||||
grossPerformance: Big;
|
||||
grossPerformancePercentage: Big;
|
||||
|
@ -66,6 +66,7 @@ describe('PortfolioCalculatorNew', () => {
|
||||
|
||||
expect(currentPositions).toEqual({
|
||||
currentValue: new Big('0'),
|
||||
errors: [],
|
||||
grossPerformance: new Big('-12.6'),
|
||||
grossPerformancePercentage: new Big('-0.0440867739678096571'),
|
||||
hasErrors: false,
|
||||
|
@ -55,6 +55,7 @@ describe('PortfolioCalculatorNew', () => {
|
||||
|
||||
expect(currentPositions).toEqual({
|
||||
currentValue: new Big('297.8'),
|
||||
errors: [],
|
||||
grossPerformance: new Big('24.6'),
|
||||
grossPerformancePercentage: new Big('0.09004392386530014641'),
|
||||
hasErrors: false,
|
||||
|
@ -1,7 +1,11 @@
|
||||
import { TimelineInfoInterface } from '@ghostfolio/api/app/portfolio/interfaces/timeline-info.interface';
|
||||
import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces';
|
||||
import { DATE_FORMAT, parseDate, resetHours } from '@ghostfolio/common/helper';
|
||||
import { TimelinePosition } from '@ghostfolio/common/interfaces';
|
||||
import {
|
||||
ResponseError,
|
||||
TimelinePosition,
|
||||
UniqueAsset
|
||||
} from '@ghostfolio/common/interfaces';
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { Type as TypeOfOrder } from '@prisma/client';
|
||||
import Big from 'big.js';
|
||||
@ -232,6 +236,8 @@ export class PortfolioCalculatorNew {
|
||||
const positions: TimelinePosition[] = [];
|
||||
let hasAnySymbolMetricsErrors = false;
|
||||
|
||||
const errors: ResponseError['errors'] = [];
|
||||
|
||||
for (const item of lastTransactionPoint.items) {
|
||||
const marketValue = marketSymbolMap[todayString]?.[item.symbol];
|
||||
|
||||
@ -272,12 +278,17 @@ export class PortfolioCalculatorNew {
|
||||
symbol: item.symbol,
|
||||
transactionCount: item.transactionCount
|
||||
});
|
||||
|
||||
if (hasErrors) {
|
||||
errors.push({ dataSource: item.dataSource, symbol: item.symbol });
|
||||
}
|
||||
}
|
||||
|
||||
const overall = this.calculateOverallPerformance(positions, initialValues);
|
||||
|
||||
return {
|
||||
...overall,
|
||||
errors,
|
||||
positions,
|
||||
hasErrors: hasAnySymbolMetricsErrors || overall.hasErrors
|
||||
};
|
||||
@ -447,7 +458,8 @@ export class PortfolioCalculatorNew {
|
||||
);
|
||||
} else if (!currentPosition.quantity.eq(0)) {
|
||||
Logger.warn(
|
||||
`Missing initial value for symbol ${currentPosition.symbol} at ${currentPosition.firstBuyDate}`
|
||||
`Missing initial value for symbol ${currentPosition.symbol} at ${currentPosition.firstBuyDate}`,
|
||||
'PortfolioCalculatorNew'
|
||||
);
|
||||
hasErrors = true;
|
||||
}
|
||||
@ -512,7 +524,8 @@ export class PortfolioCalculatorNew {
|
||||
} catch (error) {
|
||||
Logger.error(
|
||||
`Failed to fetch info for date ${startDate} with exception`,
|
||||
error
|
||||
error,
|
||||
'PortfolioCalculatorNew'
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
@ -238,7 +238,10 @@ export class PortfolioCalculator {
|
||||
if (!marketSymbolMap[nextDate]?.[item.symbol]) {
|
||||
invalidSymbols.push(item.symbol);
|
||||
hasErrors = true;
|
||||
Logger.warn(`Missing value for symbol ${item.symbol} at ${nextDate}`);
|
||||
Logger.warn(
|
||||
`Missing value for symbol ${item.symbol} at ${nextDate}`,
|
||||
'PortfolioCalculator'
|
||||
);
|
||||
continue;
|
||||
}
|
||||
let lastInvestment: Big = new Big(0);
|
||||
@ -270,7 +273,8 @@ export class PortfolioCalculator {
|
||||
invalidSymbols.push(item.symbol);
|
||||
hasErrors = true;
|
||||
Logger.warn(
|
||||
`Missing value for symbol ${item.symbol} at ${currentDate}`
|
||||
`Missing value for symbol ${item.symbol} at ${currentDate}`,
|
||||
'PortfolioCalculator'
|
||||
);
|
||||
continue;
|
||||
}
|
||||
@ -514,7 +518,8 @@ export class PortfolioCalculator {
|
||||
);
|
||||
} else if (!currentPosition.quantity.eq(0)) {
|
||||
Logger.warn(
|
||||
`Missing initial value for symbol ${currentPosition.symbol} at ${currentPosition.firstBuyDate}`
|
||||
`Missing initial value for symbol ${currentPosition.symbol} at ${currentPosition.firstBuyDate}`,
|
||||
'PortfolioCalculator'
|
||||
);
|
||||
hasErrors = true;
|
||||
}
|
||||
@ -581,7 +586,8 @@ export class PortfolioCalculator {
|
||||
} catch (error) {
|
||||
Logger.error(
|
||||
`Failed to fetch info for date ${startDate} with exception`,
|
||||
error
|
||||
error,
|
||||
'PortfolioCalculator'
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ import {
|
||||
PortfolioChart,
|
||||
PortfolioDetails,
|
||||
PortfolioInvestments,
|
||||
PortfolioPerformance,
|
||||
PortfolioPerformanceResponse,
|
||||
PortfolioPublicDetails,
|
||||
PortfolioReport,
|
||||
PortfolioSummary
|
||||
@ -204,10 +204,11 @@ export class PortfolioController {
|
||||
|
||||
@Get('performance')
|
||||
@UseGuards(AuthGuard('jwt'))
|
||||
@UseInterceptors(TransformDataSourceInResponseInterceptor)
|
||||
public async getPerformance(
|
||||
@Headers('impersonation-id') impersonationId: string,
|
||||
@Query('range') range
|
||||
): Promise<{ hasErrors: boolean; performance: PortfolioPerformance }> {
|
||||
): Promise<PortfolioPerformanceResponse> {
|
||||
const performanceInformation = await this.portfolioServiceStrategy
|
||||
.get()
|
||||
.getPerformance(impersonationId, range);
|
||||
@ -333,6 +334,7 @@ export class PortfolioController {
|
||||
'currentNetPerformance',
|
||||
'currentValue',
|
||||
'dividend',
|
||||
'emergencyFund',
|
||||
'fees',
|
||||
'items',
|
||||
'netWorth',
|
||||
|
@ -5,6 +5,8 @@ import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.s
|
||||
import { PortfolioOrder } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-order.interface';
|
||||
import { TimelineSpecification } from '@ghostfolio/api/app/portfolio/interfaces/timeline-specification.interface';
|
||||
import { TransactionPoint } from '@ghostfolio/api/app/portfolio/interfaces/transaction-point.interface';
|
||||
import { UserSettings } from '@ghostfolio/api/app/user/interfaces/user-settings.interface';
|
||||
import { UserService } from '@ghostfolio/api/app/user/user.service';
|
||||
import { AccountClusterRiskCurrentInvestment } from '@ghostfolio/api/models/rules/account-cluster-risk/current-investment';
|
||||
import { AccountClusterRiskInitialInvestment } from '@ghostfolio/api/models/rules/account-cluster-risk/initial-investment';
|
||||
import { AccountClusterRiskSingleAccount } from '@ghostfolio/api/models/rules/account-cluster-risk/single-account';
|
||||
@ -19,12 +21,16 @@ import { ImpersonationService } from '@ghostfolio/api/services/impersonation.ser
|
||||
import { MarketState } from '@ghostfolio/api/services/interfaces/interfaces';
|
||||
import { EnhancedSymbolProfile } from '@ghostfolio/api/services/interfaces/symbol-profile.interface';
|
||||
import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile.service';
|
||||
import { UNKNOWN_KEY, baseCurrency } from '@ghostfolio/common/config';
|
||||
import {
|
||||
ASSET_SUB_CLASS_EMERGENCY_FUND,
|
||||
UNKNOWN_KEY,
|
||||
baseCurrency
|
||||
} from '@ghostfolio/common/config';
|
||||
import { DATE_FORMAT, parseDate } from '@ghostfolio/common/helper';
|
||||
import {
|
||||
Accounts,
|
||||
PortfolioDetails,
|
||||
PortfolioPerformance,
|
||||
PortfolioPerformanceResponse,
|
||||
PortfolioReport,
|
||||
PortfolioSummary,
|
||||
Position,
|
||||
@ -76,7 +82,8 @@ export class PortfolioServiceNew {
|
||||
private readonly orderService: OrderService,
|
||||
@Inject(REQUEST) private readonly request: RequestWithUser,
|
||||
private readonly rulesService: RulesService,
|
||||
private readonly symbolProfileService: SymbolProfileService
|
||||
private readonly symbolProfileService: SymbolProfileService,
|
||||
private readonly userService: UserService
|
||||
) {}
|
||||
|
||||
public async getAccounts(aUserId: string): Promise<AccountWithValue[]> {
|
||||
@ -100,15 +107,22 @@ export class PortfolioServiceNew {
|
||||
}
|
||||
}
|
||||
|
||||
const value = details.accounts[account.id]?.current ?? 0;
|
||||
|
||||
const result = {
|
||||
...account,
|
||||
transactionCount,
|
||||
convertedBalance: this.exchangeRateDataService.toCurrency(
|
||||
value,
|
||||
balanceInBaseCurrency: this.exchangeRateDataService.toCurrency(
|
||||
account.balance,
|
||||
account.currency,
|
||||
userCurrency
|
||||
),
|
||||
value: details.accounts[account.id]?.current ?? 0
|
||||
valueInBaseCurrency: this.exchangeRateDataService.toCurrency(
|
||||
value,
|
||||
account.currency,
|
||||
userCurrency
|
||||
)
|
||||
};
|
||||
|
||||
delete result.Order;
|
||||
@ -119,17 +133,26 @@ export class PortfolioServiceNew {
|
||||
|
||||
public async getAccountsWithAggregations(aUserId: string): Promise<Accounts> {
|
||||
const accounts = await this.getAccounts(aUserId);
|
||||
let totalBalance = 0;
|
||||
let totalValue = 0;
|
||||
let totalBalanceInBaseCurrency = new Big(0);
|
||||
let totalValueInBaseCurrency = new Big(0);
|
||||
let transactionCount = 0;
|
||||
|
||||
for (const account of accounts) {
|
||||
totalBalance += account.convertedBalance;
|
||||
totalValue += account.value;
|
||||
totalBalanceInBaseCurrency = totalBalanceInBaseCurrency.plus(
|
||||
account.balanceInBaseCurrency
|
||||
);
|
||||
totalValueInBaseCurrency = totalValueInBaseCurrency.plus(
|
||||
account.valueInBaseCurrency
|
||||
);
|
||||
transactionCount += account.transactionCount;
|
||||
}
|
||||
|
||||
return { accounts, totalBalance, totalValue, transactionCount };
|
||||
return {
|
||||
accounts,
|
||||
transactionCount,
|
||||
totalBalanceInBaseCurrency: totalBalanceInBaseCurrency.toNumber(),
|
||||
totalValueInBaseCurrency: totalValueInBaseCurrency.toNumber()
|
||||
};
|
||||
}
|
||||
|
||||
public async getInvestments(
|
||||
@ -279,7 +302,11 @@ export class PortfolioServiceNew {
|
||||
aDateRange: DateRange = 'max'
|
||||
): Promise<PortfolioDetails & { hasErrors: boolean }> {
|
||||
const userId = await this.getUserId(aImpersonationId, aUserId);
|
||||
const user = await this.userService.user({ id: userId });
|
||||
|
||||
const emergencyFund = new Big(
|
||||
(user.Settings?.settings as UserSettings)?.emergencyFund ?? 0
|
||||
);
|
||||
const userCurrency = this.request.user?.Settings?.currency ?? baseCurrency;
|
||||
|
||||
const { orders, portfolioOrders, transactionPoints } =
|
||||
@ -293,13 +320,11 @@ export class PortfolioServiceNew {
|
||||
orders: portfolioOrders
|
||||
});
|
||||
|
||||
if (transactionPoints?.length <= 0) {
|
||||
return { accounts: {}, holdings: {}, hasErrors: false };
|
||||
}
|
||||
|
||||
portfolioCalculator.setTransactionPoints(transactionPoints);
|
||||
|
||||
const portfolioStart = parseDate(transactionPoints[0].date);
|
||||
const portfolioStart = parseDate(
|
||||
transactionPoints[0]?.date ?? format(new Date(), DATE_FORMAT)
|
||||
);
|
||||
const startDate = this.getStartDate(aDateRange, portfolioStart);
|
||||
const currentPositions = await portfolioCalculator.getCurrentPositions(
|
||||
startDate
|
||||
@ -312,9 +337,11 @@ export class PortfolioServiceNew {
|
||||
|
||||
const holdings: PortfolioDetails['holdings'] = {};
|
||||
const totalInvestment = currentPositions.totalInvestment.plus(
|
||||
cashDetails.balance
|
||||
cashDetails.balanceInBaseCurrency
|
||||
);
|
||||
const totalValue = currentPositions.currentValue.plus(
|
||||
cashDetails.balanceInBaseCurrency
|
||||
);
|
||||
const totalValue = currentPositions.currentValue.plus(cashDetails.balance);
|
||||
|
||||
const dataGatheringItems = currentPositions.positions.map((position) => {
|
||||
return {
|
||||
@ -377,6 +404,7 @@ export class PortfolioServiceNew {
|
||||
|
||||
const cashPositions = await this.getCashPositions({
|
||||
cashDetails,
|
||||
emergencyFund,
|
||||
userCurrency,
|
||||
investment: totalInvestment,
|
||||
value: totalValue
|
||||
@ -434,7 +462,7 @@ export class PortfolioServiceNew {
|
||||
};
|
||||
}
|
||||
|
||||
const positionCurrency = orders[0].currency;
|
||||
const positionCurrency = orders[0].SymbolProfile.currency;
|
||||
const [SymbolProfile] = await this.symbolProfileService.getSymbolProfiles([
|
||||
aSymbol
|
||||
]);
|
||||
@ -444,13 +472,13 @@ export class PortfolioServiceNew {
|
||||
return order.type === 'BUY' || order.type === 'SELL';
|
||||
})
|
||||
.map((order) => ({
|
||||
currency: order.currency,
|
||||
dataSource: order.SymbolProfile?.dataSource ?? order.dataSource,
|
||||
currency: order.SymbolProfile.currency,
|
||||
dataSource: order.SymbolProfile.dataSource,
|
||||
date: format(order.date, DATE_FORMAT),
|
||||
fee: new Big(order.fee),
|
||||
name: order.SymbolProfile?.name,
|
||||
quantity: new Big(order.quantity),
|
||||
symbol: order.symbol,
|
||||
symbol: order.SymbolProfile.symbol,
|
||||
type: order.type,
|
||||
unitPrice: new Big(order.unitPrice)
|
||||
}));
|
||||
@ -714,7 +742,7 @@ export class PortfolioServiceNew {
|
||||
public async getPerformance(
|
||||
aImpersonationId: string,
|
||||
aDateRange: DateRange = 'max'
|
||||
): Promise<{ hasErrors: boolean; performance: PortfolioPerformance }> {
|
||||
): Promise<PortfolioPerformanceResponse> {
|
||||
const userId = await this.getUserId(aImpersonationId, this.request.user.id);
|
||||
|
||||
const { portfolioOrders, transactionPoints } =
|
||||
@ -760,6 +788,7 @@ export class PortfolioServiceNew {
|
||||
currentPositions.netPerformancePercentage.toNumber();
|
||||
|
||||
return {
|
||||
errors: currentPositions.errors,
|
||||
hasErrors: currentPositions.hasErrors || hasErrors,
|
||||
performance: {
|
||||
currentGrossPerformance,
|
||||
@ -866,10 +895,11 @@ export class PortfolioServiceNew {
|
||||
public async getSummary(aImpersonationId: string): Promise<PortfolioSummary> {
|
||||
const userCurrency = this.request.user.Settings.currency;
|
||||
const userId = await this.getUserId(aImpersonationId, this.request.user.id);
|
||||
const user = await this.userService.user({ id: userId });
|
||||
|
||||
const performanceInformation = await this.getPerformance(aImpersonationId);
|
||||
|
||||
const { balance } = await this.accountService.getCashDetails(
|
||||
const { balanceInBaseCurrency } = await this.accountService.getCashDetails(
|
||||
userId,
|
||||
userCurrency
|
||||
);
|
||||
@ -878,6 +908,9 @@ export class PortfolioServiceNew {
|
||||
userId
|
||||
});
|
||||
const dividend = this.getDividend(orders).toNumber();
|
||||
const emergencyFund = new Big(
|
||||
(user.Settings?.settings as UserSettings)?.emergencyFund ?? 0
|
||||
);
|
||||
const fees = this.getFees(orders).toNumber();
|
||||
const firstOrderDate = orders[0]?.date;
|
||||
const items = this.getItems(orders).toNumber();
|
||||
@ -885,9 +918,10 @@ export class PortfolioServiceNew {
|
||||
const totalBuy = this.getTotalByType(orders, userCurrency, 'BUY');
|
||||
const totalSell = this.getTotalByType(orders, userCurrency, 'SELL');
|
||||
|
||||
const cash = new Big(balanceInBaseCurrency).minus(emergencyFund).toNumber();
|
||||
const committedFunds = new Big(totalBuy).minus(totalSell);
|
||||
|
||||
const netWorth = new Big(balance)
|
||||
const netWorth = new Big(balanceInBaseCurrency)
|
||||
.plus(performanceInformation.performance.currentValue)
|
||||
.plus(items)
|
||||
.toNumber();
|
||||
@ -910,6 +944,7 @@ export class PortfolioServiceNew {
|
||||
return {
|
||||
...performanceInformation.performance,
|
||||
annualizedPerformancePercent,
|
||||
cash,
|
||||
dividend,
|
||||
fees,
|
||||
firstOrderDate,
|
||||
@ -917,8 +952,8 @@ export class PortfolioServiceNew {
|
||||
netWorth,
|
||||
totalBuy,
|
||||
totalSell,
|
||||
cash: balance,
|
||||
committedFunds: committedFunds.toNumber(),
|
||||
emergencyFund: emergencyFund.toNumber(),
|
||||
ordersCount: orders.filter((order) => {
|
||||
return order.type === 'BUY' || order.type === 'SELL';
|
||||
}).length
|
||||
@ -927,16 +962,18 @@ export class PortfolioServiceNew {
|
||||
|
||||
private async getCashPositions({
|
||||
cashDetails,
|
||||
emergencyFund,
|
||||
investment,
|
||||
userCurrency,
|
||||
value
|
||||
}: {
|
||||
cashDetails: CashDetails;
|
||||
emergencyFund: Big;
|
||||
investment: Big;
|
||||
value: Big;
|
||||
userCurrency: string;
|
||||
}) {
|
||||
const cashPositions = {};
|
||||
const cashPositions: PortfolioDetails['holdings'] = {};
|
||||
|
||||
for (const account of cashDetails.accounts) {
|
||||
const convertedBalance = this.exchangeRateDataService.toCurrency(
|
||||
@ -960,6 +997,7 @@ export class PortfolioServiceNew {
|
||||
assetSubClass: AssetClass.CASH,
|
||||
countries: [],
|
||||
currency: account.currency,
|
||||
dataSource: undefined,
|
||||
grossPerformance: 0,
|
||||
grossPerformancePercent: 0,
|
||||
investment: convertedBalance,
|
||||
@ -977,6 +1015,28 @@ export class PortfolioServiceNew {
|
||||
}
|
||||
}
|
||||
|
||||
if (emergencyFund.gt(0)) {
|
||||
cashPositions[ASSET_SUB_CLASS_EMERGENCY_FUND] = {
|
||||
...cashPositions[userCurrency],
|
||||
assetSubClass: ASSET_SUB_CLASS_EMERGENCY_FUND,
|
||||
investment: emergencyFund.toNumber(),
|
||||
name: ASSET_SUB_CLASS_EMERGENCY_FUND,
|
||||
symbol: ASSET_SUB_CLASS_EMERGENCY_FUND,
|
||||
value: emergencyFund.toNumber()
|
||||
};
|
||||
|
||||
cashPositions[userCurrency].investment = new Big(
|
||||
cashPositions[userCurrency].investment
|
||||
)
|
||||
.minus(emergencyFund)
|
||||
.toNumber();
|
||||
cashPositions[userCurrency].value = new Big(
|
||||
cashPositions[userCurrency].value
|
||||
)
|
||||
.minus(emergencyFund)
|
||||
.toNumber();
|
||||
}
|
||||
|
||||
for (const symbol of Object.keys(cashPositions)) {
|
||||
// Calculate allocations for each currency
|
||||
cashPositions[symbol].allocationCurrent = new Big(
|
||||
@ -1006,7 +1066,7 @@ export class PortfolioServiceNew {
|
||||
.map((order) => {
|
||||
return this.exchangeRateDataService.toCurrency(
|
||||
new Big(order.quantity).mul(order.unitPrice).toNumber(),
|
||||
order.currency,
|
||||
order.SymbolProfile.currency,
|
||||
this.request.user.Settings.currency
|
||||
);
|
||||
})
|
||||
@ -1025,7 +1085,7 @@ export class PortfolioServiceNew {
|
||||
.map((order) => {
|
||||
return this.exchangeRateDataService.toCurrency(
|
||||
order.fee,
|
||||
order.currency,
|
||||
order.SymbolProfile.currency,
|
||||
this.request.user.Settings.currency
|
||||
);
|
||||
})
|
||||
@ -1047,7 +1107,7 @@ export class PortfolioServiceNew {
|
||||
.map((order) => {
|
||||
return this.exchangeRateDataService.toCurrency(
|
||||
new Big(order.quantity).mul(order.unitPrice).toNumber(),
|
||||
order.currency,
|
||||
order.SymbolProfile.currency,
|
||||
this.request.user.Settings.currency
|
||||
);
|
||||
})
|
||||
@ -1100,24 +1160,24 @@ export class PortfolioServiceNew {
|
||||
}
|
||||
|
||||
const portfolioOrders: PortfolioOrder[] = orders.map((order) => ({
|
||||
currency: order.currency,
|
||||
dataSource: order.SymbolProfile?.dataSource ?? order.dataSource,
|
||||
currency: order.SymbolProfile.currency,
|
||||
dataSource: order.SymbolProfile.dataSource,
|
||||
date: format(order.date, DATE_FORMAT),
|
||||
fee: new Big(
|
||||
this.exchangeRateDataService.toCurrency(
|
||||
order.fee,
|
||||
order.currency,
|
||||
order.SymbolProfile.currency,
|
||||
userCurrency
|
||||
)
|
||||
),
|
||||
name: order.SymbolProfile?.name,
|
||||
quantity: new Big(order.quantity),
|
||||
symbol: order.symbol,
|
||||
symbol: order.SymbolProfile.symbol,
|
||||
type: order.type,
|
||||
unitPrice: new Big(
|
||||
this.exchangeRateDataService.toCurrency(
|
||||
order.unitPrice,
|
||||
order.currency,
|
||||
order.SymbolProfile.currency,
|
||||
userCurrency
|
||||
)
|
||||
)
|
||||
@ -1153,22 +1213,18 @@ export class PortfolioServiceNew {
|
||||
return accountId === account.id;
|
||||
});
|
||||
|
||||
const convertedBalance = this.exchangeRateDataService.toCurrency(
|
||||
account.balance,
|
||||
account.currency,
|
||||
userCurrency
|
||||
);
|
||||
accounts[account.id] = {
|
||||
balance: convertedBalance,
|
||||
balance: account.balance,
|
||||
currency: account.currency,
|
||||
current: convertedBalance,
|
||||
current: account.balance,
|
||||
name: account.name,
|
||||
original: convertedBalance
|
||||
original: account.balance
|
||||
};
|
||||
|
||||
for (const order of ordersByAccount) {
|
||||
let currentValueOfSymbol =
|
||||
order.quantity * portfolioItemsNow[order.symbol].marketPrice;
|
||||
order.quantity *
|
||||
portfolioItemsNow[order.SymbolProfile.symbol].marketPrice;
|
||||
let originalValueOfSymbol = order.quantity * order.unitPrice;
|
||||
|
||||
if (order.type === 'SELL') {
|
||||
@ -1218,7 +1274,7 @@ export class PortfolioServiceNew {
|
||||
.map((order) => {
|
||||
return this.exchangeRateDataService.toCurrency(
|
||||
order.quantity * order.unitPrice,
|
||||
order.currency,
|
||||
order.SymbolProfile.currency,
|
||||
currency
|
||||
);
|
||||
})
|
||||
|
@ -6,6 +6,8 @@ import { PortfolioOrder } from '@ghostfolio/api/app/portfolio/interfaces/portfol
|
||||
import { TimelineSpecification } from '@ghostfolio/api/app/portfolio/interfaces/timeline-specification.interface';
|
||||
import { TransactionPoint } from '@ghostfolio/api/app/portfolio/interfaces/transaction-point.interface';
|
||||
import { PortfolioCalculator } from '@ghostfolio/api/app/portfolio/portfolio-calculator';
|
||||
import { UserSettings } from '@ghostfolio/api/app/user/interfaces/user-settings.interface';
|
||||
import { UserService } from '@ghostfolio/api/app/user/user.service';
|
||||
import { AccountClusterRiskCurrentInvestment } from '@ghostfolio/api/models/rules/account-cluster-risk/current-investment';
|
||||
import { AccountClusterRiskInitialInvestment } from '@ghostfolio/api/models/rules/account-cluster-risk/initial-investment';
|
||||
import { AccountClusterRiskSingleAccount } from '@ghostfolio/api/models/rules/account-cluster-risk/single-account';
|
||||
@ -20,12 +22,16 @@ import { ImpersonationService } from '@ghostfolio/api/services/impersonation.ser
|
||||
import { MarketState } from '@ghostfolio/api/services/interfaces/interfaces';
|
||||
import { EnhancedSymbolProfile } from '@ghostfolio/api/services/interfaces/symbol-profile.interface';
|
||||
import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile.service';
|
||||
import { UNKNOWN_KEY, baseCurrency } from '@ghostfolio/common/config';
|
||||
import {
|
||||
ASSET_SUB_CLASS_EMERGENCY_FUND,
|
||||
UNKNOWN_KEY,
|
||||
baseCurrency
|
||||
} from '@ghostfolio/common/config';
|
||||
import { DATE_FORMAT, parseDate } from '@ghostfolio/common/helper';
|
||||
import {
|
||||
Accounts,
|
||||
PortfolioDetails,
|
||||
PortfolioPerformance,
|
||||
PortfolioPerformanceResponse,
|
||||
PortfolioReport,
|
||||
PortfolioSummary,
|
||||
Position,
|
||||
@ -75,7 +81,8 @@ export class PortfolioService {
|
||||
private readonly orderService: OrderService,
|
||||
@Inject(REQUEST) private readonly request: RequestWithUser,
|
||||
private readonly rulesService: RulesService,
|
||||
private readonly symbolProfileService: SymbolProfileService
|
||||
private readonly symbolProfileService: SymbolProfileService,
|
||||
private readonly userService: UserService
|
||||
) {}
|
||||
|
||||
public async getAccounts(aUserId: string): Promise<AccountWithValue[]> {
|
||||
@ -99,15 +106,22 @@ export class PortfolioService {
|
||||
}
|
||||
}
|
||||
|
||||
const value = details.accounts[account.id]?.current ?? 0;
|
||||
|
||||
const result = {
|
||||
...account,
|
||||
transactionCount,
|
||||
convertedBalance: this.exchangeRateDataService.toCurrency(
|
||||
value,
|
||||
balanceInBaseCurrency: this.exchangeRateDataService.toCurrency(
|
||||
account.balance,
|
||||
account.currency,
|
||||
userCurrency
|
||||
),
|
||||
value: details.accounts[account.id]?.current ?? 0
|
||||
valueInBaseCurrency: this.exchangeRateDataService.toCurrency(
|
||||
value,
|
||||
account.currency,
|
||||
userCurrency
|
||||
)
|
||||
};
|
||||
|
||||
delete result.Order;
|
||||
@ -118,17 +132,26 @@ export class PortfolioService {
|
||||
|
||||
public async getAccountsWithAggregations(aUserId: string): Promise<Accounts> {
|
||||
const accounts = await this.getAccounts(aUserId);
|
||||
let totalBalance = 0;
|
||||
let totalValue = 0;
|
||||
let totalBalanceInBaseCurrency = new Big(0);
|
||||
let totalValueInBaseCurrency = new Big(0);
|
||||
let transactionCount = 0;
|
||||
|
||||
for (const account of accounts) {
|
||||
totalBalance += account.convertedBalance;
|
||||
totalValue += account.value;
|
||||
totalBalanceInBaseCurrency = totalBalanceInBaseCurrency.plus(
|
||||
account.balanceInBaseCurrency
|
||||
);
|
||||
totalValueInBaseCurrency = totalValueInBaseCurrency.plus(
|
||||
account.valueInBaseCurrency
|
||||
);
|
||||
transactionCount += account.transactionCount;
|
||||
}
|
||||
|
||||
return { accounts, totalBalance, totalValue, transactionCount };
|
||||
return {
|
||||
accounts,
|
||||
transactionCount,
|
||||
totalBalanceInBaseCurrency: totalBalanceInBaseCurrency.toNumber(),
|
||||
totalValueInBaseCurrency: totalValueInBaseCurrency.toNumber()
|
||||
};
|
||||
}
|
||||
|
||||
public async getInvestments(
|
||||
@ -270,7 +293,11 @@ export class PortfolioService {
|
||||
aDateRange: DateRange = 'max'
|
||||
): Promise<PortfolioDetails & { hasErrors: boolean }> {
|
||||
const userId = await this.getUserId(aImpersonationId, aUserId);
|
||||
const user = await this.userService.user({ id: userId });
|
||||
|
||||
const emergencyFund = new Big(
|
||||
(user.Settings?.settings as UserSettings)?.emergencyFund ?? 0
|
||||
);
|
||||
const userCurrency = this.request.user?.Settings?.currency ?? baseCurrency;
|
||||
const portfolioCalculator = new PortfolioCalculator(
|
||||
this.currentRateService,
|
||||
@ -281,13 +308,11 @@ export class PortfolioService {
|
||||
userId
|
||||
});
|
||||
|
||||
if (transactionPoints?.length <= 0) {
|
||||
return { accounts: {}, holdings: {}, hasErrors: false };
|
||||
}
|
||||
|
||||
portfolioCalculator.setTransactionPoints(transactionPoints);
|
||||
|
||||
const portfolioStart = parseDate(transactionPoints[0].date);
|
||||
const portfolioStart = parseDate(
|
||||
transactionPoints[0]?.date ?? format(new Date(), DATE_FORMAT)
|
||||
);
|
||||
const startDate = this.getStartDate(aDateRange, portfolioStart);
|
||||
const currentPositions = await portfolioCalculator.getCurrentPositions(
|
||||
startDate
|
||||
@ -300,9 +325,11 @@ export class PortfolioService {
|
||||
|
||||
const holdings: PortfolioDetails['holdings'] = {};
|
||||
const totalInvestment = currentPositions.totalInvestment.plus(
|
||||
cashDetails.balance
|
||||
cashDetails.balanceInBaseCurrency
|
||||
);
|
||||
const totalValue = currentPositions.currentValue.plus(
|
||||
cashDetails.balanceInBaseCurrency
|
||||
);
|
||||
const totalValue = currentPositions.currentValue.plus(cashDetails.balance);
|
||||
|
||||
const dataGatheringItems = currentPositions.positions.map((position) => {
|
||||
return {
|
||||
@ -365,6 +392,7 @@ export class PortfolioService {
|
||||
|
||||
const cashPositions = await this.getCashPositions({
|
||||
cashDetails,
|
||||
emergencyFund,
|
||||
userCurrency,
|
||||
investment: totalInvestment,
|
||||
value: totalValue
|
||||
@ -422,7 +450,7 @@ export class PortfolioService {
|
||||
};
|
||||
}
|
||||
|
||||
const positionCurrency = orders[0].currency;
|
||||
const positionCurrency = orders[0].SymbolProfile.currency;
|
||||
const [SymbolProfile] = await this.symbolProfileService.getSymbolProfiles([
|
||||
aSymbol
|
||||
]);
|
||||
@ -432,13 +460,13 @@ export class PortfolioService {
|
||||
return order.type === 'BUY' || order.type === 'SELL';
|
||||
})
|
||||
.map((order) => ({
|
||||
currency: order.currency,
|
||||
dataSource: order.SymbolProfile?.dataSource ?? order.dataSource,
|
||||
currency: order.SymbolProfile.currency,
|
||||
dataSource: order.SymbolProfile.dataSource,
|
||||
date: format(order.date, DATE_FORMAT),
|
||||
fee: new Big(order.fee),
|
||||
name: order.SymbolProfile?.name,
|
||||
quantity: new Big(order.quantity),
|
||||
symbol: order.symbol,
|
||||
symbol: order.SymbolProfile.symbol,
|
||||
type: order.type,
|
||||
unitPrice: new Big(order.unitPrice)
|
||||
}));
|
||||
@ -696,7 +724,7 @@ export class PortfolioService {
|
||||
public async getPerformance(
|
||||
aImpersonationId: string,
|
||||
aDateRange: DateRange = 'max'
|
||||
): Promise<{ hasErrors: boolean; performance: PortfolioPerformance }> {
|
||||
): Promise<PortfolioPerformanceResponse> {
|
||||
const userId = await this.getUserId(aImpersonationId, this.request.user.id);
|
||||
|
||||
const portfolioCalculator = new PortfolioCalculator(
|
||||
@ -845,10 +873,11 @@ export class PortfolioService {
|
||||
public async getSummary(aImpersonationId: string): Promise<PortfolioSummary> {
|
||||
const userCurrency = this.request.user.Settings.currency;
|
||||
const userId = await this.getUserId(aImpersonationId, this.request.user.id);
|
||||
const user = await this.userService.user({ id: userId });
|
||||
|
||||
const performanceInformation = await this.getPerformance(aImpersonationId);
|
||||
|
||||
const { balance } = await this.accountService.getCashDetails(
|
||||
const { balanceInBaseCurrency } = await this.accountService.getCashDetails(
|
||||
userId,
|
||||
userCurrency
|
||||
);
|
||||
@ -857,6 +886,9 @@ export class PortfolioService {
|
||||
userId
|
||||
});
|
||||
const dividend = this.getDividend(orders).toNumber();
|
||||
const emergencyFund = new Big(
|
||||
(user.Settings?.settings as UserSettings)?.emergencyFund ?? 0
|
||||
);
|
||||
const fees = this.getFees(orders).toNumber();
|
||||
const firstOrderDate = orders[0]?.date;
|
||||
const items = this.getItems(orders).toNumber();
|
||||
@ -864,15 +896,17 @@ export class PortfolioService {
|
||||
const totalBuy = this.getTotalByType(orders, userCurrency, 'BUY');
|
||||
const totalSell = this.getTotalByType(orders, userCurrency, 'SELL');
|
||||
|
||||
const cash = new Big(balanceInBaseCurrency).minus(emergencyFund).toNumber();
|
||||
const committedFunds = new Big(totalBuy).minus(totalSell);
|
||||
|
||||
const netWorth = new Big(balance)
|
||||
const netWorth = new Big(balanceInBaseCurrency)
|
||||
.plus(performanceInformation.performance.currentValue)
|
||||
.plus(items)
|
||||
.toNumber();
|
||||
|
||||
return {
|
||||
...performanceInformation.performance,
|
||||
cash,
|
||||
dividend,
|
||||
fees,
|
||||
firstOrderDate,
|
||||
@ -882,8 +916,8 @@ export class PortfolioService {
|
||||
totalSell,
|
||||
annualizedPerformancePercent:
|
||||
performanceInformation.performance.annualizedPerformancePercent,
|
||||
cash: balance,
|
||||
committedFunds: committedFunds.toNumber(),
|
||||
emergencyFund: emergencyFund.toNumber(),
|
||||
ordersCount: orders.filter((order) => {
|
||||
return order.type === 'BUY' || order.type === 'SELL';
|
||||
}).length
|
||||
@ -892,16 +926,18 @@ export class PortfolioService {
|
||||
|
||||
private async getCashPositions({
|
||||
cashDetails,
|
||||
emergencyFund,
|
||||
investment,
|
||||
userCurrency,
|
||||
value
|
||||
}: {
|
||||
cashDetails: CashDetails;
|
||||
emergencyFund: Big;
|
||||
investment: Big;
|
||||
userCurrency: string;
|
||||
value: Big;
|
||||
}) {
|
||||
const cashPositions = {};
|
||||
const cashPositions: PortfolioDetails['holdings'] = {};
|
||||
|
||||
for (const account of cashDetails.accounts) {
|
||||
const convertedBalance = this.exchangeRateDataService.toCurrency(
|
||||
@ -925,6 +961,7 @@ export class PortfolioService {
|
||||
assetSubClass: AssetClass.CASH,
|
||||
countries: [],
|
||||
currency: account.currency,
|
||||
dataSource: undefined,
|
||||
grossPerformance: 0,
|
||||
grossPerformancePercent: 0,
|
||||
investment: convertedBalance,
|
||||
@ -942,6 +979,28 @@ export class PortfolioService {
|
||||
}
|
||||
}
|
||||
|
||||
if (emergencyFund.gt(0)) {
|
||||
cashPositions[ASSET_SUB_CLASS_EMERGENCY_FUND] = {
|
||||
...cashPositions[userCurrency],
|
||||
assetSubClass: ASSET_SUB_CLASS_EMERGENCY_FUND,
|
||||
investment: emergencyFund.toNumber(),
|
||||
name: ASSET_SUB_CLASS_EMERGENCY_FUND,
|
||||
symbol: ASSET_SUB_CLASS_EMERGENCY_FUND,
|
||||
value: emergencyFund.toNumber()
|
||||
};
|
||||
|
||||
cashPositions[userCurrency].investment = new Big(
|
||||
cashPositions[userCurrency].investment
|
||||
)
|
||||
.minus(emergencyFund)
|
||||
.toNumber();
|
||||
cashPositions[userCurrency].value = new Big(
|
||||
cashPositions[userCurrency].value
|
||||
)
|
||||
.minus(emergencyFund)
|
||||
.toNumber();
|
||||
}
|
||||
|
||||
for (const symbol of Object.keys(cashPositions)) {
|
||||
// Calculate allocations for each currency
|
||||
cashPositions[symbol].allocationCurrent = new Big(
|
||||
@ -971,7 +1030,7 @@ export class PortfolioService {
|
||||
.map((order) => {
|
||||
return this.exchangeRateDataService.toCurrency(
|
||||
new Big(order.quantity).mul(order.unitPrice).toNumber(),
|
||||
order.currency,
|
||||
order.SymbolProfile.currency,
|
||||
this.request.user.Settings.currency
|
||||
);
|
||||
})
|
||||
@ -990,7 +1049,7 @@ export class PortfolioService {
|
||||
.map((order) => {
|
||||
return this.exchangeRateDataService.toCurrency(
|
||||
order.fee,
|
||||
order.currency,
|
||||
order.SymbolProfile.currency,
|
||||
this.request.user.Settings.currency
|
||||
);
|
||||
})
|
||||
@ -1012,7 +1071,7 @@ export class PortfolioService {
|
||||
.map((order) => {
|
||||
return this.exchangeRateDataService.toCurrency(
|
||||
new Big(order.quantity).mul(order.unitPrice).toNumber(),
|
||||
order.currency,
|
||||
order.SymbolProfile.currency,
|
||||
this.request.user.Settings.currency
|
||||
);
|
||||
})
|
||||
@ -1064,24 +1123,24 @@ export class PortfolioService {
|
||||
}
|
||||
|
||||
const portfolioOrders: PortfolioOrder[] = orders.map((order) => ({
|
||||
currency: order.currency,
|
||||
dataSource: order.SymbolProfile?.dataSource ?? order.dataSource,
|
||||
currency: order.SymbolProfile.currency,
|
||||
dataSource: order.SymbolProfile.dataSource,
|
||||
date: format(order.date, DATE_FORMAT),
|
||||
fee: new Big(
|
||||
this.exchangeRateDataService.toCurrency(
|
||||
order.fee,
|
||||
order.currency,
|
||||
order.SymbolProfile.currency,
|
||||
userCurrency
|
||||
)
|
||||
),
|
||||
name: order.SymbolProfile?.name,
|
||||
quantity: new Big(order.quantity),
|
||||
symbol: order.symbol,
|
||||
symbol: order.SymbolProfile.symbol,
|
||||
type: order.type,
|
||||
unitPrice: new Big(
|
||||
this.exchangeRateDataService.toCurrency(
|
||||
order.unitPrice,
|
||||
order.currency,
|
||||
order.SymbolProfile.currency,
|
||||
userCurrency
|
||||
)
|
||||
)
|
||||
@ -1113,22 +1172,18 @@ export class PortfolioService {
|
||||
return accountId === account.id;
|
||||
});
|
||||
|
||||
const convertedBalance = this.exchangeRateDataService.toCurrency(
|
||||
account.balance,
|
||||
account.currency,
|
||||
userCurrency
|
||||
);
|
||||
accounts[account.id] = {
|
||||
balance: convertedBalance,
|
||||
balance: account.balance,
|
||||
currency: account.currency,
|
||||
current: convertedBalance,
|
||||
current: account.balance,
|
||||
name: account.name,
|
||||
original: convertedBalance
|
||||
original: account.balance
|
||||
};
|
||||
|
||||
for (const order of ordersByAccount) {
|
||||
let currentValueOfSymbol =
|
||||
order.quantity * portfolioItemsNow[order.symbol].marketPrice;
|
||||
order.quantity *
|
||||
portfolioItemsNow[order.SymbolProfile.symbol].marketPrice;
|
||||
let originalValueOfSymbol = order.quantity * order.unitPrice;
|
||||
|
||||
if (order.type === 'SELL') {
|
||||
@ -1178,7 +1233,7 @@ export class PortfolioService {
|
||||
.map((order) => {
|
||||
return this.exchangeRateDataService.toCurrency(
|
||||
order.quantity * order.unitPrice,
|
||||
order.currency,
|
||||
order.SymbolProfile.currency,
|
||||
currency
|
||||
);
|
||||
})
|
||||
|
@ -46,22 +46,25 @@ export class SubscriptionController {
|
||||
((await this.propertyService.getByKey(PROPERTY_COUPONS)) as Coupon[]) ??
|
||||
[];
|
||||
|
||||
const isValid = coupons.some((coupon) => {
|
||||
return coupon.code === couponCode;
|
||||
const coupon = coupons.find((currentCoupon) => {
|
||||
return currentCoupon.code === couponCode;
|
||||
});
|
||||
|
||||
if (!isValid) {
|
||||
if (coupon === undefined) {
|
||||
throw new HttpException(
|
||||
getReasonPhrase(StatusCodes.BAD_REQUEST),
|
||||
StatusCodes.BAD_REQUEST
|
||||
);
|
||||
}
|
||||
|
||||
await this.subscriptionService.createSubscription(this.request.user.id);
|
||||
await this.subscriptionService.createSubscription({
|
||||
duration: coupon.duration,
|
||||
userId: this.request.user.id
|
||||
});
|
||||
|
||||
// Destroy coupon
|
||||
coupons = coupons.filter((coupon) => {
|
||||
return coupon.code !== couponCode;
|
||||
coupons = coupons.filter((currentCoupon) => {
|
||||
return currentCoupon.code !== couponCode;
|
||||
});
|
||||
await this.propertyService.put({
|
||||
key: PROPERTY_COUPONS,
|
||||
@ -69,7 +72,8 @@ export class SubscriptionController {
|
||||
});
|
||||
|
||||
Logger.log(
|
||||
`Subscription for user '${this.request.user.id}' has been created with coupon`
|
||||
`Subscription for user '${this.request.user.id}' has been created with a coupon for ${coupon.duration}`,
|
||||
'SubscriptionController'
|
||||
);
|
||||
|
||||
return {
|
||||
@ -84,7 +88,10 @@ export class SubscriptionController {
|
||||
req.query.checkoutSessionId
|
||||
);
|
||||
|
||||
Logger.log(`Subscription for user '${userId}' has been created via Stripe`);
|
||||
Logger.log(
|
||||
`Subscription for user '${userId}' has been created via Stripe`,
|
||||
'SubscriptionController'
|
||||
);
|
||||
|
||||
res.redirect(`${this.configurationService.get('ROOT_URL')}/account`);
|
||||
}
|
||||
@ -101,7 +108,7 @@ export class SubscriptionController {
|
||||
userId: this.request.user.id
|
||||
});
|
||||
} catch (error) {
|
||||
Logger.error(error);
|
||||
Logger.error(error, 'SubscriptionController');
|
||||
|
||||
throw new HttpException(
|
||||
getReasonPhrase(StatusCodes.BAD_REQUEST),
|
||||
|
@ -2,8 +2,9 @@ import { ConfigurationService } from '@ghostfolio/api/services/configuration.ser
|
||||
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
||||
import { SubscriptionType } from '@ghostfolio/common/types/subscription.type';
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { Subscription, User } from '@prisma/client';
|
||||
import { addDays, isBefore } from 'date-fns';
|
||||
import { Subscription } from '@prisma/client';
|
||||
import { addMilliseconds, isBefore } from 'date-fns';
|
||||
import ms, { StringValue } from 'ms';
|
||||
import Stripe from 'stripe';
|
||||
|
||||
@Injectable()
|
||||
@ -64,13 +65,19 @@ export class SubscriptionService {
|
||||
};
|
||||
}
|
||||
|
||||
public async createSubscription(aUserId: string) {
|
||||
public async createSubscription({
|
||||
duration = '1 year',
|
||||
userId
|
||||
}: {
|
||||
duration?: StringValue;
|
||||
userId: string;
|
||||
}) {
|
||||
await this.prismaService.subscription.create({
|
||||
data: {
|
||||
expiresAt: addDays(new Date(), 365),
|
||||
expiresAt: addMilliseconds(new Date(), ms(duration)),
|
||||
User: {
|
||||
connect: {
|
||||
id: aUserId
|
||||
id: userId
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -83,7 +90,7 @@ export class SubscriptionService {
|
||||
aCheckoutSessionId
|
||||
);
|
||||
|
||||
await this.createSubscription(session.client_reference_id);
|
||||
await this.createSubscription({ userId: session.client_reference_id });
|
||||
|
||||
await this.stripe.customers.update(session.customer as string, {
|
||||
description: session.client_reference_id
|
||||
@ -91,7 +98,7 @@ export class SubscriptionService {
|
||||
|
||||
return session.client_reference_id;
|
||||
} catch (error) {
|
||||
Logger.error(error);
|
||||
Logger.error(error, 'SubscriptionService');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -95,7 +95,7 @@ export class SymbolService {
|
||||
results.items = items;
|
||||
return results;
|
||||
} catch (error) {
|
||||
Logger.error(error);
|
||||
Logger.error(error, 'SymbolService');
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
export interface UserSettings {
|
||||
emergencyFund?: number;
|
||||
isNewCalculationEngine?: boolean;
|
||||
isRestrictedView?: boolean;
|
||||
}
|
||||
|
@ -1,6 +1,10 @@
|
||||
import { IsBoolean, IsOptional } from 'class-validator';
|
||||
import { IsBoolean, IsNumber, IsOptional } from 'class-validator';
|
||||
|
||||
export class UpdateUserSettingDto {
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
emergencyFund?: number;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
isNewCalculationEngine?: boolean;
|
||||
|
@ -23,7 +23,6 @@ import {
|
||||
import { REQUEST } from '@nestjs/core';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
import { Provider, Role } from '@prisma/client';
|
||||
import { User as UserModel } from '@prisma/client';
|
||||
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
|
||||
|
||||
|
@ -15,7 +15,7 @@ import {
|
||||
} from '@ghostfolio/common/permissions';
|
||||
import { SubscriptionType } from '@ghostfolio/common/types/subscription.type';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Prisma, Provider, Role, User, ViewMode } from '@prisma/client';
|
||||
import { Prisma, Role, User, ViewMode } from '@prisma/client';
|
||||
|
||||
import { UserSettingsParams } from './interfaces/user-settings-params.interface';
|
||||
import { UserSettings } from './interfaces/user-settings.interface';
|
||||
|
@ -32,7 +32,6 @@ export class TransformDataSourceInResponseInterceptor<T>
|
||||
activity.SymbolProfile.dataSource = encodeDataSource(
|
||||
activity.SymbolProfile.dataSource
|
||||
);
|
||||
activity.dataSource = encodeDataSource(activity.dataSource);
|
||||
return activity;
|
||||
});
|
||||
}
|
||||
@ -41,6 +40,14 @@ export class TransformDataSourceInResponseInterceptor<T>
|
||||
data.dataSource = encodeDataSource(data.dataSource);
|
||||
}
|
||||
|
||||
if (data.errors) {
|
||||
for (const error of data.errors) {
|
||||
if (error.dataSource) {
|
||||
error.dataSource = encodeDataSource(error.dataSource);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (data.holdings) {
|
||||
for (const symbol of Object.keys(data.holdings)) {
|
||||
if (data.holdings[symbol].dataSource) {
|
||||
@ -58,13 +65,6 @@ export class TransformDataSourceInResponseInterceptor<T>
|
||||
});
|
||||
}
|
||||
|
||||
if (data.orders) {
|
||||
data.orders.map((order) => {
|
||||
order.dataSource = encodeDataSource(order.dataSource);
|
||||
return order;
|
||||
});
|
||||
}
|
||||
|
||||
if (data.positions) {
|
||||
data.positions.map((position) => {
|
||||
position.dataSource = encodeDataSource(position.dataSource);
|
||||
|
@ -4,6 +4,7 @@ import {
|
||||
PROPERTY_LOCKED_DATA_GATHERING
|
||||
} from '@ghostfolio/common/config';
|
||||
import { DATE_FORMAT, resetHours } from '@ghostfolio/common/helper';
|
||||
import { UniqueAsset } from '@ghostfolio/common/interfaces';
|
||||
import { Inject, Injectable, Logger } from '@nestjs/common';
|
||||
import { DataSource } from '@prisma/client';
|
||||
import {
|
||||
@ -39,7 +40,7 @@ export class DataGatheringService {
|
||||
const isDataGatheringNeeded = await this.isDataGatheringNeeded();
|
||||
|
||||
if (isDataGatheringNeeded) {
|
||||
Logger.log('7d data gathering has been started.');
|
||||
Logger.log('7d data gathering has been started.', 'DataGatheringService');
|
||||
console.time('data-gathering-7d');
|
||||
|
||||
await this.prismaService.property.create({
|
||||
@ -63,7 +64,7 @@ export class DataGatheringService {
|
||||
where: { key: PROPERTY_LAST_DATA_GATHERING }
|
||||
});
|
||||
} catch (error) {
|
||||
Logger.error(error);
|
||||
Logger.error(error, 'DataGatheringService');
|
||||
}
|
||||
|
||||
await this.prismaService.property.delete({
|
||||
@ -72,7 +73,10 @@ export class DataGatheringService {
|
||||
}
|
||||
});
|
||||
|
||||
Logger.log('7d data gathering has been completed.');
|
||||
Logger.log(
|
||||
'7d data gathering has been completed.',
|
||||
'DataGatheringService'
|
||||
);
|
||||
console.timeEnd('data-gathering-7d');
|
||||
}
|
||||
}
|
||||
@ -83,7 +87,10 @@ export class DataGatheringService {
|
||||
});
|
||||
|
||||
if (!isDataGatheringLocked) {
|
||||
Logger.log('Max data gathering has been started.');
|
||||
Logger.log(
|
||||
'Max data gathering has been started.',
|
||||
'DataGatheringService'
|
||||
);
|
||||
console.time('data-gathering-max');
|
||||
|
||||
await this.prismaService.property.create({
|
||||
@ -107,7 +114,7 @@ export class DataGatheringService {
|
||||
where: { key: PROPERTY_LAST_DATA_GATHERING }
|
||||
});
|
||||
} catch (error) {
|
||||
Logger.error(error);
|
||||
Logger.error(error, 'DataGatheringService');
|
||||
}
|
||||
|
||||
await this.prismaService.property.delete({
|
||||
@ -116,24 +123,24 @@ export class DataGatheringService {
|
||||
}
|
||||
});
|
||||
|
||||
Logger.log('Max data gathering has been completed.');
|
||||
Logger.log(
|
||||
'Max data gathering has been completed.',
|
||||
'DataGatheringService'
|
||||
);
|
||||
console.timeEnd('data-gathering-max');
|
||||
}
|
||||
}
|
||||
|
||||
public async gatherSymbol({
|
||||
dataSource,
|
||||
symbol
|
||||
}: {
|
||||
dataSource: DataSource;
|
||||
symbol: string;
|
||||
}) {
|
||||
public async gatherSymbol({ dataSource, symbol }: UniqueAsset) {
|
||||
const isDataGatheringLocked = await this.prismaService.property.findUnique({
|
||||
where: { key: PROPERTY_LOCKED_DATA_GATHERING }
|
||||
});
|
||||
|
||||
if (!isDataGatheringLocked) {
|
||||
Logger.log(`Symbol data gathering for ${symbol} has been started.`);
|
||||
Logger.log(
|
||||
`Symbol data gathering for ${symbol} has been started.`,
|
||||
'DataGatheringService'
|
||||
);
|
||||
console.time('data-gathering-symbol');
|
||||
|
||||
await this.prismaService.property.create({
|
||||
@ -164,7 +171,7 @@ export class DataGatheringService {
|
||||
where: { key: PROPERTY_LAST_DATA_GATHERING }
|
||||
});
|
||||
} catch (error) {
|
||||
Logger.error(error);
|
||||
Logger.error(error, 'DataGatheringService');
|
||||
}
|
||||
|
||||
await this.prismaService.property.delete({
|
||||
@ -173,7 +180,10 @@ export class DataGatheringService {
|
||||
}
|
||||
});
|
||||
|
||||
Logger.log(`Symbol data gathering for ${symbol} has been completed.`);
|
||||
Logger.log(
|
||||
`Symbol data gathering for ${symbol} has been completed.`,
|
||||
'DataGatheringService'
|
||||
);
|
||||
console.timeEnd('data-gathering-symbol');
|
||||
}
|
||||
}
|
||||
@ -210,14 +220,17 @@ export class DataGatheringService {
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
Logger.error(error);
|
||||
Logger.error(error, 'DataGatheringService');
|
||||
} finally {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
public async gatherProfileData(aDataGatheringItems?: IDataGatheringItem[]) {
|
||||
Logger.log('Profile data gathering has been started.');
|
||||
Logger.log(
|
||||
'Profile data gathering has been started.',
|
||||
'DataGatheringService'
|
||||
);
|
||||
console.time('data-gathering-profile');
|
||||
|
||||
let dataGatheringItems = aDataGatheringItems?.filter(
|
||||
@ -253,7 +266,8 @@ export class DataGatheringService {
|
||||
} catch (error) {
|
||||
Logger.error(
|
||||
`Failed to enhance data for symbol ${symbol} by ${dataEnhancer.getName()}`,
|
||||
error
|
||||
error,
|
||||
'DataGatheringService'
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -299,11 +313,18 @@ export class DataGatheringService {
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
Logger.error(`${symbol}: ${error?.meta?.cause}`);
|
||||
Logger.error(
|
||||
`${symbol}: ${error?.meta?.cause}`,
|
||||
error,
|
||||
'DataGatheringService'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Logger.log('Profile data gathering has been completed.');
|
||||
Logger.log(
|
||||
'Profile data gathering has been completed.',
|
||||
'DataGatheringService'
|
||||
);
|
||||
console.timeEnd('data-gathering-profile');
|
||||
}
|
||||
|
||||
@ -366,7 +387,8 @@ export class DataGatheringService {
|
||||
`Failed to gather data for symbol ${symbol} from ${dataSource} at ${format(
|
||||
currentDate,
|
||||
DATE_FORMAT
|
||||
)}.`
|
||||
)}.`,
|
||||
'DataGatheringService'
|
||||
);
|
||||
}
|
||||
|
||||
@ -382,14 +404,15 @@ export class DataGatheringService {
|
||||
}
|
||||
} catch (error) {
|
||||
hasError = true;
|
||||
Logger.error(error);
|
||||
Logger.error(error, 'DataGatheringService');
|
||||
}
|
||||
|
||||
if (symbolCounter > 0 && symbolCounter % 100 === 0) {
|
||||
Logger.log(
|
||||
`Data gathering progress: ${(
|
||||
this.dataGatheringProgress * 100
|
||||
).toFixed(2)}%`
|
||||
).toFixed(2)}%`,
|
||||
'DataGatheringService'
|
||||
);
|
||||
}
|
||||
|
||||
@ -479,7 +502,7 @@ export class DataGatheringService {
|
||||
}
|
||||
|
||||
public async reset() {
|
||||
Logger.log('Data gathering has been reset.');
|
||||
Logger.log('Data gathering has been reset.', 'DataGatheringService');
|
||||
|
||||
await this.prismaService.property.deleteMany({
|
||||
where: {
|
||||
@ -554,19 +577,24 @@ export class DataGatheringService {
|
||||
}
|
||||
|
||||
private async getSymbolsProfileData(): Promise<IDataGatheringItem[]> {
|
||||
const distinctOrders = await this.prismaService.order.findMany({
|
||||
distinct: ['symbol'],
|
||||
orderBy: [{ symbol: 'asc' }],
|
||||
select: { dataSource: true, symbol: true }
|
||||
const symbolProfiles = await this.prismaService.symbolProfile.findMany({
|
||||
orderBy: [{ symbol: 'asc' }]
|
||||
});
|
||||
|
||||
return distinctOrders.filter((distinctOrder) => {
|
||||
return (
|
||||
distinctOrder.dataSource !== DataSource.GHOSTFOLIO &&
|
||||
distinctOrder.dataSource !== DataSource.MANUAL &&
|
||||
distinctOrder.dataSource !== DataSource.RAKUTEN
|
||||
);
|
||||
});
|
||||
return symbolProfiles
|
||||
.filter((symbolProfile) => {
|
||||
return (
|
||||
symbolProfile.dataSource !== DataSource.GHOSTFOLIO &&
|
||||
symbolProfile.dataSource !== DataSource.MANUAL &&
|
||||
symbolProfile.dataSource !== DataSource.RAKUTEN
|
||||
);
|
||||
})
|
||||
.map((symbolProfile) => {
|
||||
return {
|
||||
dataSource: symbolProfile.dataSource,
|
||||
symbol: symbolProfile.symbol
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private async isDataGatheringNeeded() {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface';
|
||||
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
|
||||
import { DataProviderInterface } from '@ghostfolio/api/services/data-provider/interfaces/data-provider.interface';
|
||||
import {
|
||||
IDataProviderHistoricalResponse,
|
||||
IDataProviderResponse
|
||||
@ -10,7 +11,6 @@ import { Injectable, Logger } from '@nestjs/common';
|
||||
import { DataSource, SymbolProfile } from '@prisma/client';
|
||||
import { isAfter, isBefore, parse } from 'date-fns';
|
||||
|
||||
import { DataProviderInterface } from '../interfaces/data-provider.interface';
|
||||
import { IAlphaVantageHistoricalResponse } from './interfaces/interfaces';
|
||||
|
||||
@Injectable()
|
||||
@ -76,7 +76,7 @@ export class AlphaVantageService implements DataProviderInterface {
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
Logger.error(error, symbol);
|
||||
Logger.error(error, 'AlphaVantageService');
|
||||
|
||||
return {};
|
||||
}
|
||||
|
@ -82,7 +82,7 @@ export class DataProviderService {
|
||||
return r;
|
||||
}, {});
|
||||
} catch (error) {
|
||||
Logger.error(error);
|
||||
Logger.error(error, 'DataProviderService');
|
||||
} finally {
|
||||
return response;
|
||||
}
|
||||
|
@ -7,11 +7,7 @@ import {
|
||||
} from '@ghostfolio/api/services/interfaces/interfaces';
|
||||
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
||||
import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile.service';
|
||||
import {
|
||||
DATE_FORMAT,
|
||||
getYesterday,
|
||||
isGhostfolioScraperApiSymbol
|
||||
} from '@ghostfolio/common/helper';
|
||||
import { DATE_FORMAT, getYesterday } from '@ghostfolio/common/helper';
|
||||
import { Granularity } from '@ghostfolio/common/types';
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { DataSource, SymbolProfile } from '@prisma/client';
|
||||
@ -29,7 +25,7 @@ export class GhostfolioScraperApiService implements DataProviderInterface {
|
||||
) {}
|
||||
|
||||
public canHandle(symbol: string) {
|
||||
return isGhostfolioScraperApiSymbol(symbol);
|
||||
return true;
|
||||
}
|
||||
|
||||
public async getAssetProfile(
|
||||
@ -73,7 +69,7 @@ export class GhostfolioScraperApiService implements DataProviderInterface {
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
Logger.error(error);
|
||||
Logger.error(error, 'GhostfolioScraperApiService');
|
||||
}
|
||||
|
||||
return {};
|
||||
@ -114,7 +110,7 @@ export class GhostfolioScraperApiService implements DataProviderInterface {
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
Logger.error(error);
|
||||
Logger.error(error, 'GhostfolioScraperApiService');
|
||||
}
|
||||
|
||||
return {};
|
||||
|
@ -72,7 +72,7 @@ export class GoogleSheetsService implements DataProviderInterface {
|
||||
[symbol]: historicalData
|
||||
};
|
||||
} catch (error) {
|
||||
Logger.error(error);
|
||||
Logger.error(error, 'GoogleSheetsService');
|
||||
}
|
||||
|
||||
return {};
|
||||
@ -121,7 +121,7 @@ export class GoogleSheetsService implements DataProviderInterface {
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
Logger.error(error);
|
||||
Logger.error(error, 'GoogleSheetsService');
|
||||
}
|
||||
|
||||
return {};
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface';
|
||||
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
|
||||
import { DataProviderInterface } from '@ghostfolio/api/services/data-provider/interfaces/data-provider.interface';
|
||||
import {
|
||||
IDataProviderHistoricalResponse,
|
||||
IDataProviderResponse,
|
||||
@ -14,8 +15,6 @@ import { DataSource, SymbolProfile } from '@prisma/client';
|
||||
import * as bent from 'bent';
|
||||
import { format, subMonths, subWeeks, subYears } from 'date-fns';
|
||||
|
||||
import { DataProviderInterface } from '../interfaces/data-provider.interface';
|
||||
|
||||
@Injectable()
|
||||
export class RakutenRapidApiService implements DataProviderInterface {
|
||||
public static FEAR_AND_GREED_INDEX_NAME = 'Fear & Greed Index';
|
||||
@ -126,7 +125,7 @@ export class RakutenRapidApiService implements DataProviderInterface {
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
Logger.error(error);
|
||||
Logger.error(error, 'RakutenRapidApiService');
|
||||
}
|
||||
|
||||
return {};
|
||||
@ -161,7 +160,7 @@ export class RakutenRapidApiService implements DataProviderInterface {
|
||||
const { fgi } = await get();
|
||||
return fgi;
|
||||
} catch (error) {
|
||||
Logger.error(error);
|
||||
Logger.error(error, 'RakutenRapidApiService');
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface';
|
||||
import { CryptocurrencyService } from '@ghostfolio/api/services/cryptocurrency/cryptocurrency.service';
|
||||
import { DataProviderInterface } from '@ghostfolio/api/services/data-provider/interfaces/data-provider.interface';
|
||||
import {
|
||||
IDataProviderHistoricalResponse,
|
||||
IDataProviderResponse,
|
||||
@ -19,9 +20,7 @@ import * as bent from 'bent';
|
||||
import Big from 'big.js';
|
||||
import { countries } from 'countries-list';
|
||||
import { addDays, format, isSameDay } from 'date-fns';
|
||||
import yahooFinance2 from 'yahoo-finance2';
|
||||
|
||||
import { DataProviderInterface } from '../interfaces/data-provider.interface';
|
||||
import yahooFinance from 'yahoo-finance2';
|
||||
|
||||
@Injectable()
|
||||
export class YahooFinanceService implements DataProviderInterface {
|
||||
@ -80,7 +79,7 @@ export class YahooFinanceService implements DataProviderInterface {
|
||||
|
||||
try {
|
||||
const symbol = this.convertToYahooFinanceSymbol(aSymbol);
|
||||
const assetProfile = await yahooFinance2.quoteSummary(symbol, {
|
||||
const assetProfile = await yahooFinance.quoteSummary(symbol, {
|
||||
modules: ['price', 'summaryProfile']
|
||||
});
|
||||
|
||||
@ -143,7 +142,7 @@ export class YahooFinanceService implements DataProviderInterface {
|
||||
const yahooFinanceSymbol = this.convertToYahooFinanceSymbol(aSymbol);
|
||||
|
||||
try {
|
||||
const historicalResult = await yahooFinance2.historical(
|
||||
const historicalResult = await yahooFinance.historical(
|
||||
yahooFinanceSymbol,
|
||||
{
|
||||
interval: '1d',
|
||||
@ -178,7 +177,8 @@ export class YahooFinanceService implements DataProviderInterface {
|
||||
return response;
|
||||
} catch (error) {
|
||||
Logger.warn(
|
||||
`Skipping yahooFinance2.getHistorical("${aSymbol}"): [${error.name}] ${error.message}`
|
||||
`Skipping yahooFinance2.getHistorical("${aSymbol}"): [${error.name}] ${error.message}`,
|
||||
'YahooFinanceService'
|
||||
);
|
||||
|
||||
return {};
|
||||
@ -202,7 +202,7 @@ export class YahooFinanceService implements DataProviderInterface {
|
||||
try {
|
||||
const response: { [symbol: string]: IDataProviderResponse } = {};
|
||||
|
||||
const quotes = await yahooFinance2.quote(yahooFinanceSymbols);
|
||||
const quotes = await yahooFinance.quote(yahooFinanceSymbols);
|
||||
|
||||
for (const quote of quotes) {
|
||||
// Convert symbols back
|
||||
@ -233,7 +233,7 @@ export class YahooFinanceService implements DataProviderInterface {
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
Logger.error(error);
|
||||
Logger.error(error, 'YahooFinanceService');
|
||||
|
||||
return {};
|
||||
}
|
||||
@ -297,7 +297,7 @@ export class YahooFinanceService implements DataProviderInterface {
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
Logger.error(error);
|
||||
Logger.error(error, 'YahooFinanceService');
|
||||
}
|
||||
|
||||
return { items };
|
||||
|
@ -114,6 +114,10 @@ export class ExchangeRateDataService {
|
||||
aFromCurrency: string,
|
||||
aToCurrency: string
|
||||
) {
|
||||
if (aValue === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const hasNaN = Object.values(this.exchangeRates).some((exchangeRate) => {
|
||||
return isNaN(exchangeRate);
|
||||
});
|
||||
@ -145,7 +149,8 @@ export class ExchangeRateDataService {
|
||||
|
||||
// Fallback with error, if currencies are not available
|
||||
Logger.error(
|
||||
`No exchange rate has been found for ${aFromCurrency}${aToCurrency}`
|
||||
`No exchange rate has been found for ${aFromCurrency}${aToCurrency}`,
|
||||
'ExchangeRateDataService'
|
||||
);
|
||||
return aValue;
|
||||
}
|
||||
@ -187,12 +192,7 @@ export class ExchangeRateDataService {
|
||||
await this.prismaService.symbolProfile.findMany({
|
||||
distinct: ['currency'],
|
||||
orderBy: [{ currency: 'asc' }],
|
||||
select: { currency: true },
|
||||
where: {
|
||||
currency: {
|
||||
not: null
|
||||
}
|
||||
}
|
||||
select: { currency: true }
|
||||
})
|
||||
).forEach((symbolProfile) => {
|
||||
currencies.push(symbolProfile.currency);
|
||||
@ -206,7 +206,7 @@ export class ExchangeRateDataService {
|
||||
currencies = currencies.concat(customCurrencies);
|
||||
}
|
||||
|
||||
return uniq(currencies).sort();
|
||||
return uniq(currencies).filter(Boolean).sort();
|
||||
}
|
||||
|
||||
private prepareCurrencyPairs(aCurrencies: string[]) {
|
||||
|
@ -2,6 +2,7 @@ import { UpdateMarketDataDto } from '@ghostfolio/api/app/admin/update-market-dat
|
||||
import { DateQuery } from '@ghostfolio/api/app/portfolio/interfaces/date-query.interface';
|
||||
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
||||
import { resetHours } from '@ghostfolio/common/helper';
|
||||
import { UniqueAsset } from '@ghostfolio/common/interfaces';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { DataSource, MarketData, Prisma } from '@prisma/client';
|
||||
|
||||
@ -9,13 +10,7 @@ import { DataSource, MarketData, Prisma } from '@prisma/client';
|
||||
export class MarketDataService {
|
||||
public constructor(private readonly prismaService: PrismaService) {}
|
||||
|
||||
public async deleteMany({
|
||||
dataSource,
|
||||
symbol
|
||||
}: {
|
||||
dataSource: DataSource;
|
||||
symbol: string;
|
||||
}) {
|
||||
public async deleteMany({ dataSource, symbol }: UniqueAsset) {
|
||||
return this.prismaService.marketData.deleteMany({
|
||||
where: {
|
||||
dataSource,
|
||||
|
@ -54,11 +54,12 @@ export class TwitterBotService {
|
||||
);
|
||||
|
||||
Logger.log(
|
||||
`Fear & Greed Index has been tweeted: https://twitter.com/ghostfolio_/status/${createdTweet.id}`
|
||||
`Fear & Greed Index has been tweeted: https://twitter.com/ghostfolio_/status/${createdTweet.id}`,
|
||||
'TwitterBotService'
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
Logger.error(error);
|
||||
Logger.error(error, 'TwitterBotService');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -86,7 +86,7 @@
|
||||
class="d-inline-block justify-content-end"
|
||||
[isCurrency]="true"
|
||||
[locale]="locale"
|
||||
[value]="element.convertedBalance"
|
||||
[value]="element.balance"
|
||||
></gf-value>
|
||||
</td>
|
||||
<td *matFooterCellDef class="px-1 text-right" mat-footer-cell>
|
||||
@ -94,7 +94,7 @@
|
||||
class="d-inline-block justify-content-end"
|
||||
[isCurrency]="true"
|
||||
[locale]="locale"
|
||||
[value]="totalBalance"
|
||||
[value]="totalBalanceInBaseCurrency"
|
||||
></gf-value>
|
||||
</td>
|
||||
</ng-container>
|
||||
@ -116,7 +116,7 @@
|
||||
class="d-inline-block justify-content-end"
|
||||
[isCurrency]="true"
|
||||
[locale]="locale"
|
||||
[value]="totalValue"
|
||||
[value]="totalValueInBaseCurrency"
|
||||
></gf-value>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
@ -24,8 +24,8 @@ export class AccountsTableComponent implements OnChanges, OnDestroy, OnInit {
|
||||
@Input() deviceType: string;
|
||||
@Input() locale: string;
|
||||
@Input() showActions: boolean;
|
||||
@Input() totalBalance: number;
|
||||
@Input() totalValue: number;
|
||||
@Input() totalBalanceInBaseCurrency: number;
|
||||
@Input() totalValueInBaseCurrency: number;
|
||||
@Input() transactionCount: number;
|
||||
|
||||
@Output() accountDeleted = new EventEmitter<string>();
|
||||
|
@ -8,6 +8,7 @@ import {
|
||||
import { AdminService } from '@ghostfolio/client/services/admin.service';
|
||||
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||
import { DEFAULT_DATE_FORMAT } from '@ghostfolio/common/config';
|
||||
import { UniqueAsset } from '@ghostfolio/common/interfaces';
|
||||
import { AdminMarketDataItem } from '@ghostfolio/common/interfaces/admin-market-data.interface';
|
||||
import { DataSource, MarketData } from '@prisma/client';
|
||||
import { Subject } from 'rxjs';
|
||||
@ -44,39 +45,21 @@ export class AdminMarketDataComponent implements OnDestroy, OnInit {
|
||||
this.fetchAdminMarketData();
|
||||
}
|
||||
|
||||
public onDeleteProfileData({
|
||||
dataSource,
|
||||
symbol
|
||||
}: {
|
||||
dataSource: DataSource;
|
||||
symbol: string;
|
||||
}) {
|
||||
public onDeleteProfileData({ dataSource, symbol }: UniqueAsset) {
|
||||
this.adminService
|
||||
.deleteProfileData({ dataSource, symbol })
|
||||
.pipe(takeUntil(this.unsubscribeSubject))
|
||||
.subscribe(() => {});
|
||||
}
|
||||
|
||||
public onGatherProfileDataBySymbol({
|
||||
dataSource,
|
||||
symbol
|
||||
}: {
|
||||
dataSource: DataSource;
|
||||
symbol: string;
|
||||
}) {
|
||||
public onGatherProfileDataBySymbol({ dataSource, symbol }: UniqueAsset) {
|
||||
this.adminService
|
||||
.gatherProfileDataBySymbol({ dataSource, symbol })
|
||||
.pipe(takeUntil(this.unsubscribeSubject))
|
||||
.subscribe(() => {});
|
||||
}
|
||||
|
||||
public onGatherSymbol({
|
||||
dataSource,
|
||||
symbol
|
||||
}: {
|
||||
dataSource: DataSource;
|
||||
symbol: string;
|
||||
}) {
|
||||
public onGatherSymbol({ dataSource, symbol }: UniqueAsset) {
|
||||
this.adminService
|
||||
.gatherSymbol({ dataSource, symbol })
|
||||
.pipe(takeUntil(this.unsubscribeSubject))
|
||||
@ -93,13 +76,7 @@ export class AdminMarketDataComponent implements OnDestroy, OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
public setCurrentProfile({
|
||||
dataSource,
|
||||
symbol
|
||||
}: {
|
||||
dataSource: DataSource;
|
||||
symbol: string;
|
||||
}) {
|
||||
public setCurrentProfile({ dataSource, symbol }: UniqueAsset) {
|
||||
this.marketDataDetails = [];
|
||||
|
||||
if (this.currentSymbol === symbol) {
|
||||
@ -129,13 +106,7 @@ export class AdminMarketDataComponent implements OnDestroy, OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
private fetchAdminMarketDataBySymbol({
|
||||
dataSource,
|
||||
symbol
|
||||
}: {
|
||||
dataSource: DataSource;
|
||||
symbol: string;
|
||||
}) {
|
||||
private fetchAdminMarketDataBySymbol({ dataSource, symbol }: UniqueAsset) {
|
||||
this.adminService
|
||||
.fetchAdminMarketDataBySymbol({ dataSource, symbol })
|
||||
.pipe(takeUntil(this.unsubscribeSubject))
|
||||
|
@ -20,6 +20,7 @@ import {
|
||||
parseISO
|
||||
} from 'date-fns';
|
||||
import { uniq } from 'lodash';
|
||||
import { StringValue } from 'ms';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
|
||||
@ -29,6 +30,7 @@ import { takeUntil } from 'rxjs/operators';
|
||||
templateUrl: './admin-overview.html'
|
||||
})
|
||||
export class AdminOverviewComponent implements OnDestroy, OnInit {
|
||||
public couponDuration: StringValue = '30 days';
|
||||
public coupons: Coupon[];
|
||||
public customCurrencies: string[];
|
||||
public dataGatheringInProgress: boolean;
|
||||
@ -105,7 +107,10 @@ export class AdminOverviewComponent implements OnDestroy, OnInit {
|
||||
}
|
||||
|
||||
public onAddCoupon() {
|
||||
const coupons = [...this.coupons, { code: this.generateCouponCode(16) }];
|
||||
const coupons = [
|
||||
...this.coupons,
|
||||
{ code: this.generateCouponCode(16), duration: this.couponDuration }
|
||||
];
|
||||
this.putCoupons(coupons);
|
||||
}
|
||||
|
||||
@ -118,6 +123,10 @@ export class AdminOverviewComponent implements OnDestroy, OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
public onChangeCouponDuration(aCouponDuration: StringValue) {
|
||||
this.couponDuration = aCouponDuration;
|
||||
}
|
||||
|
||||
public onDeleteCoupon(aCouponCode: string) {
|
||||
const confirmation = confirm('Do you really want to delete this coupon?');
|
||||
|
||||
|
@ -156,11 +156,14 @@
|
||||
></mat-slide-toggle>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="hasPermissionForSubscription" class="d-flex my-3">
|
||||
<div
|
||||
*ngIf="hasPermissionForSubscription"
|
||||
class="d-flex my-3 subscription"
|
||||
>
|
||||
<div class="w-50" i18n>Coupons</div>
|
||||
<div class="w-50">
|
||||
<div *ngFor="let coupon of coupons">
|
||||
<span>{{ coupon.code }}</span>
|
||||
<span>{{ coupon.code }} ({{ coupon.duration }})</span>
|
||||
<button
|
||||
class="mini-icon mx-1 no-min-width px-2"
|
||||
mat-button
|
||||
@ -170,10 +173,25 @@
|
||||
</button>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<button color="primary" mat-flat-button (click)="onAddCoupon()">
|
||||
<ion-icon class="mr-1" name="add-outline"></ion-icon>
|
||||
<span i18n>Add Coupon</span>
|
||||
</button>
|
||||
<form #couponForm="ngForm">
|
||||
<mat-form-field appearance="outline" class="mr-2">
|
||||
<mat-select
|
||||
name="duration"
|
||||
[value]="couponDuration"
|
||||
(selectionChange)="onChangeCouponDuration($event.value)"
|
||||
>
|
||||
<mat-option value="30 days">30 Days</mat-option>
|
||||
<mat-option value="1 year">1 Year</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<button
|
||||
color="primary"
|
||||
mat-flat-button
|
||||
(click)="onAddCoupon()"
|
||||
>
|
||||
<span i18n>Add</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,7 +1,9 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
||||
import { CacheService } from '@ghostfolio/client/services/cache.service';
|
||||
import { GfValueModule } from '@ghostfolio/ui/value';
|
||||
@ -12,11 +14,14 @@ import { AdminOverviewComponent } from './admin-overview.component';
|
||||
declarations: [AdminOverviewComponent],
|
||||
exports: [],
|
||||
imports: [
|
||||
FormsModule,
|
||||
CommonModule,
|
||||
GfValueModule,
|
||||
MatButtonModule,
|
||||
MatCardModule,
|
||||
MatSlideToggleModule
|
||||
MatSelectModule,
|
||||
MatSlideToggleModule,
|
||||
ReactiveFormsModule
|
||||
],
|
||||
providers: [CacheService],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
|
@ -20,4 +20,10 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.subscription {
|
||||
.mat-form-field {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,12 +14,12 @@
|
||||
Accounts
|
||||
</th>
|
||||
<th class="mat-header-cell px-1 py-2 text-right" i18n>
|
||||
Transactions
|
||||
Activities
|
||||
</th>
|
||||
<th class="mat-header-cell px-1 py-2 text-right" i18n>
|
||||
Engagement per Day
|
||||
</th>
|
||||
<th class="mat-header-cell px-1 py-2" i18n>Last Activitiy</th>
|
||||
<th class="mat-header-cell px-1 py-2" i18n>Last Request</th>
|
||||
<th class="mat-header-cell px-1 py-2"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -25,7 +25,7 @@
|
||||
<a
|
||||
class="mt-3"
|
||||
i18n
|
||||
mat-button
|
||||
mat-stroked-button
|
||||
[routerLink]="['/portfolio', 'activities']"
|
||||
>Manage Activities</a
|
||||
>
|
||||
|
@ -7,7 +7,11 @@ import {
|
||||
} from '@ghostfolio/client/services/settings-storage.service';
|
||||
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
||||
import { defaultDateRangeOptions } from '@ghostfolio/common/config';
|
||||
import { PortfolioPerformance, User } from '@ghostfolio/common/interfaces';
|
||||
import {
|
||||
PortfolioPerformance,
|
||||
UniqueAsset,
|
||||
User
|
||||
} from '@ghostfolio/common/interfaces';
|
||||
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||
import { DateRange } from '@ghostfolio/common/types';
|
||||
import { LineChartItem } from '@ghostfolio/ui/line-chart/interfaces/line-chart.interface';
|
||||
@ -24,6 +28,7 @@ export class HomeOverviewComponent implements OnDestroy, OnInit {
|
||||
public dateRange: DateRange;
|
||||
public dateRangeOptions = defaultDateRangeOptions;
|
||||
public deviceType: string;
|
||||
public errors: UniqueAsset[];
|
||||
public hasError: boolean;
|
||||
public hasImpersonationId: boolean;
|
||||
public hasPermissionToCreateOrder: boolean;
|
||||
@ -126,6 +131,7 @@ export class HomeOverviewComponent implements OnDestroy, OnInit {
|
||||
.fetchPortfolioPerformance({ range: this.dateRange })
|
||||
.pipe(takeUntil(this.unsubscribeSubject))
|
||||
.subscribe((response) => {
|
||||
this.errors = response.errors;
|
||||
this.hasError = response.hasErrors;
|
||||
this.performance = response.performance;
|
||||
this.isLoadingPerformance = false;
|
||||
|
@ -28,6 +28,7 @@
|
||||
class="pb-4"
|
||||
[baseCurrency]="user?.settings?.baseCurrency"
|
||||
[deviceType]="deviceType"
|
||||
[errors]="errors"
|
||||
[hasError]="hasError"
|
||||
[isAllTimeHigh]="isAllTimeHigh"
|
||||
[isAllTimeLow]="isAllTimeLow"
|
||||
|
@ -1,7 +1,9 @@
|
||||
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
|
||||
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
||||
import { PortfolioSummary, User } from '@ghostfolio/common/interfaces';
|
||||
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
|
||||
@ -11,6 +13,8 @@ import { takeUntil } from 'rxjs/operators';
|
||||
templateUrl: './home-summary.html'
|
||||
})
|
||||
export class HomeSummaryComponent implements OnDestroy, OnInit {
|
||||
public hasImpersonationId: boolean;
|
||||
public hasPermissionToUpdateUserSettings: boolean;
|
||||
public isLoading = true;
|
||||
public summary: PortfolioSummary;
|
||||
public user: User;
|
||||
@ -23,6 +27,7 @@ export class HomeSummaryComponent implements OnDestroy, OnInit {
|
||||
public constructor(
|
||||
private changeDetectorRef: ChangeDetectorRef,
|
||||
private dataService: DataService,
|
||||
private impersonationStorageService: ImpersonationStorageService,
|
||||
private userService: UserService
|
||||
) {
|
||||
this.userService.stateChanged
|
||||
@ -31,6 +36,11 @@ export class HomeSummaryComponent implements OnDestroy, OnInit {
|
||||
if (state?.user) {
|
||||
this.user = state.user;
|
||||
|
||||
this.hasPermissionToUpdateUserSettings = hasPermission(
|
||||
this.user.permissions,
|
||||
permissions.updateUserSettings
|
||||
);
|
||||
|
||||
this.changeDetectorRef.markForCheck();
|
||||
}
|
||||
});
|
||||
@ -40,9 +50,25 @@ export class HomeSummaryComponent implements OnDestroy, OnInit {
|
||||
* Initializes the controller
|
||||
*/
|
||||
public ngOnInit() {
|
||||
this.impersonationStorageService
|
||||
.onChangeHasImpersonation()
|
||||
.pipe(takeUntil(this.unsubscribeSubject))
|
||||
.subscribe((aId) => {
|
||||
this.hasImpersonationId = !!aId;
|
||||
});
|
||||
|
||||
this.update();
|
||||
}
|
||||
|
||||
public onChangeEmergencyFund(emergencyFund: number) {
|
||||
this.dataService
|
||||
.putUserSetting({ emergencyFund })
|
||||
.pipe(takeUntil(this.unsubscribeSubject))
|
||||
.subscribe(() => {
|
||||
this.update();
|
||||
});
|
||||
}
|
||||
|
||||
public ngOnDestroy() {
|
||||
this.unsubscribeSubject.next();
|
||||
this.unsubscribeSubject.complete();
|
||||
|
@ -8,9 +8,11 @@
|
||||
<mat-card-content>
|
||||
<gf-portfolio-summary
|
||||
[baseCurrency]="user?.settings?.baseCurrency"
|
||||
[hasPermissionToUpdateUserSettings]="!hasImpersonationId && hasPermissionToUpdateUserSettings"
|
||||
[isLoading]="isLoading"
|
||||
[locale]="user?.settings?.locale"
|
||||
[summary]="summary"
|
||||
(emergencyFundChanged)="onChangeEmergencyFund($event)"
|
||||
></gf-portfolio-summary>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
|
@ -7,6 +7,7 @@
|
||||
? 'Sorry! Our data provider partner is experiencing the hiccups.'
|
||||
: ''
|
||||
"
|
||||
(click)="errors?.length > 0 && onShowErrors()"
|
||||
>
|
||||
<ion-icon
|
||||
*ngIf="hasError && !isLoading"
|
||||
|
@ -7,7 +7,10 @@ import {
|
||||
OnInit,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
import { PortfolioPerformance } from '@ghostfolio/common/interfaces';
|
||||
import {
|
||||
PortfolioPerformance,
|
||||
ResponseError
|
||||
} from '@ghostfolio/common/interfaces';
|
||||
import { CountUp } from 'countup.js';
|
||||
import { isNumber } from 'lodash';
|
||||
|
||||
@ -20,6 +23,7 @@ import { isNumber } from 'lodash';
|
||||
export class PortfolioPerformanceComponent implements OnChanges, OnInit {
|
||||
@Input() baseCurrency: string;
|
||||
@Input() deviceType: string;
|
||||
@Input() errors: ResponseError['errors'];
|
||||
@Input() hasError: boolean;
|
||||
@Input() isAllTimeHigh: boolean;
|
||||
@Input() isAllTimeLow: boolean;
|
||||
@ -69,4 +73,12 @@ export class PortfolioPerformanceComponent implements OnChanges, OnInit {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public onShowErrors() {
|
||||
const errorMessageParts = this.errors.map((error) => {
|
||||
return `${error.symbol} (${error.dataSource})`;
|
||||
});
|
||||
|
||||
alert(errorMessageParts.join('\n'));
|
||||
}
|
||||
}
|
||||
|
@ -130,6 +130,26 @@
|
||||
></gf-value>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row px-3 py-1">
|
||||
<div class="d-flex flex-grow-1" i18n>Emergency Fund</div>
|
||||
<div
|
||||
class="align-items-center d-flex justify-content-end"
|
||||
[ngClass]="{ 'cursor-pointer': hasPermissionToUpdateUserSettings }"
|
||||
(click)="hasPermissionToUpdateUserSettings && onEditEmergencyFund()"
|
||||
>
|
||||
<ion-icon
|
||||
*ngIf="hasPermissionToUpdateUserSettings && !isLoading"
|
||||
class="mr-1 text-muted"
|
||||
name="ellipsis-horizontal-circle-outline"
|
||||
></ion-icon>
|
||||
<gf-value
|
||||
class="justify-content-end"
|
||||
[currency]="baseCurrency"
|
||||
[locale]="locale"
|
||||
[value]="isLoading ? undefined : summary?.emergencyFund"
|
||||
></gf-value>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row px-3 py-1">
|
||||
<div class="d-flex flex-grow-1" i18n>Cash (Buying Power)</div>
|
||||
<div class="d-flex justify-content-end">
|
||||
|
@ -1,9 +1,11 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnInit
|
||||
OnInit,
|
||||
Output
|
||||
} from '@angular/core';
|
||||
import { PortfolioSummary } from '@ghostfolio/common/interfaces';
|
||||
import { formatDistanceToNow } from 'date-fns';
|
||||
@ -16,10 +18,13 @@ import { formatDistanceToNow } from 'date-fns';
|
||||
})
|
||||
export class PortfolioSummaryComponent implements OnChanges, OnInit {
|
||||
@Input() baseCurrency: string;
|
||||
@Input() hasPermissionToUpdateUserSettings: boolean;
|
||||
@Input() isLoading: boolean;
|
||||
@Input() locale: string;
|
||||
@Input() summary: PortfolioSummary;
|
||||
|
||||
@Output() emergencyFundChanged = new EventEmitter<number>();
|
||||
|
||||
public timeInMarket: string;
|
||||
|
||||
public constructor() {}
|
||||
@ -37,4 +42,16 @@ export class PortfolioSummaryComponent implements OnChanges, OnInit {
|
||||
this.timeInMarket = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
public onEditEmergencyFund() {
|
||||
const emergencyFundInput = prompt(
|
||||
'Please enter the amount of your emergency fund:',
|
||||
this.summary.emergencyFund.toString()
|
||||
);
|
||||
const emergencyFund = parseFloat(emergencyFundInput?.trim());
|
||||
|
||||
if (emergencyFund >= 0) {
|
||||
this.emergencyFundChanged.emit(emergencyFund);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||
import { GfValueModule } from '@ghostfolio/ui/value';
|
||||
|
||||
import { PortfolioSummaryComponent } from './portfolio-summary.component';
|
||||
@ -8,6 +8,6 @@ import { PortfolioSummaryComponent } from './portfolio-summary.component';
|
||||
declarations: [PortfolioSummaryComponent],
|
||||
exports: [PortfolioSummaryComponent],
|
||||
imports: [CommonModule, GfValueModule],
|
||||
providers: []
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
})
|
||||
export class GfPortfolioSummaryModule {}
|
||||
|
@ -2,6 +2,7 @@
|
||||
<div class="flex-nowrap no-gutters row">
|
||||
<a
|
||||
class="d-flex p-3 w-100"
|
||||
[ngClass]="{ 'cursor-default': isLoading }"
|
||||
[routerLink]="[]"
|
||||
[queryParams]="{
|
||||
dataSource: position?.dataSource,
|
||||
|
@ -2,8 +2,6 @@
|
||||
display: block;
|
||||
|
||||
.container {
|
||||
cursor: pointer;
|
||||
|
||||
gf-trend-indicator {
|
||||
padding-top: 0.15rem;
|
||||
}
|
||||
|
@ -13,8 +13,9 @@ import { MatPaginator } from '@angular/material/paginator';
|
||||
import { MatSort } from '@angular/material/sort';
|
||||
import { MatTableDataSource } from '@angular/material/table';
|
||||
import { Router } from '@angular/router';
|
||||
import { PortfolioPosition } from '@ghostfolio/common/interfaces';
|
||||
import { AssetClass, DataSource, Order as OrderModel } from '@prisma/client';
|
||||
import { ASSET_SUB_CLASS_EMERGENCY_FUND } from '@ghostfolio/common/config';
|
||||
import { PortfolioPosition, UniqueAsset } from '@ghostfolio/common/interfaces';
|
||||
import { AssetClass, Order as OrderModel } from '@prisma/client';
|
||||
import { Subject, Subscription } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
@ -39,7 +40,10 @@ export class PositionsTableComponent implements OnChanges, OnDestroy, OnInit {
|
||||
public dataSource: MatTableDataSource<PortfolioPosition> =
|
||||
new MatTableDataSource();
|
||||
public displayedColumns = [];
|
||||
public ignoreAssetSubClasses = [AssetClass.CASH.toString()];
|
||||
public ignoreAssetSubClasses = [
|
||||
AssetClass.CASH.toString(),
|
||||
ASSET_SUB_CLASS_EMERGENCY_FUND
|
||||
];
|
||||
public isLoading = true;
|
||||
public pageSize = 7;
|
||||
public routeQueryParams: Subscription;
|
||||
@ -75,13 +79,7 @@ export class PositionsTableComponent implements OnChanges, OnDestroy, OnInit {
|
||||
this.dataSource.filter = filterValue.trim().toLowerCase();
|
||||
}*/
|
||||
|
||||
public onOpenPositionDialog({
|
||||
dataSource,
|
||||
symbol
|
||||
}: {
|
||||
dataSource: DataSource;
|
||||
symbol: string;
|
||||
}): void {
|
||||
public onOpenPositionDialog({ dataSource, symbol }: UniqueAsset): void {
|
||||
this.router.navigate([], {
|
||||
queryParams: { dataSource, symbol, positionDetailDialog: true }
|
||||
});
|
||||
|
@ -28,8 +28,8 @@ export class AccountsPageComponent implements OnDestroy, OnInit {
|
||||
public hasPermissionToCreateAccount: boolean;
|
||||
public hasPermissionToDeleteAccount: boolean;
|
||||
public routeQueryParams: Subscription;
|
||||
public totalBalance = 0;
|
||||
public totalValue = 0;
|
||||
public totalBalanceInBaseCurrency = 0;
|
||||
public totalValueInBaseCurrency = 0;
|
||||
public transactionCount = 0;
|
||||
public user: User;
|
||||
|
||||
@ -106,18 +106,25 @@ export class AccountsPageComponent implements OnDestroy, OnInit {
|
||||
this.dataService
|
||||
.fetchAccounts()
|
||||
.pipe(takeUntil(this.unsubscribeSubject))
|
||||
.subscribe(({ accounts, totalBalance, totalValue, transactionCount }) => {
|
||||
this.accounts = accounts;
|
||||
this.totalBalance = totalBalance;
|
||||
this.totalValue = totalValue;
|
||||
this.transactionCount = transactionCount;
|
||||
.subscribe(
|
||||
({
|
||||
accounts,
|
||||
totalBalanceInBaseCurrency,
|
||||
totalValueInBaseCurrency,
|
||||
transactionCount
|
||||
}) => {
|
||||
this.accounts = accounts;
|
||||
this.totalBalanceInBaseCurrency = totalBalanceInBaseCurrency;
|
||||
this.totalValueInBaseCurrency = totalValueInBaseCurrency;
|
||||
this.transactionCount = transactionCount;
|
||||
|
||||
if (this.accounts?.length <= 0) {
|
||||
this.router.navigate([], { queryParams: { createDialog: true } });
|
||||
if (this.accounts?.length <= 0) {
|
||||
this.router.navigate([], { queryParams: { createDialog: true } });
|
||||
}
|
||||
|
||||
this.changeDetectorRef.markForCheck();
|
||||
}
|
||||
|
||||
this.changeDetectorRef.markForCheck();
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
public onDeleteAccount(aId: string) {
|
||||
|
@ -9,8 +9,8 @@
|
||||
[deviceType]="deviceType"
|
||||
[locale]="user?.settings?.locale"
|
||||
[showActions]="!hasImpersonationId && hasPermissionToDeleteAccount && !user.settings.isRestrictedView"
|
||||
[totalBalance]="totalBalance"
|
||||
[totalValue]="totalValue"
|
||||
[totalBalanceInBaseCurrency]="totalBalanceInBaseCurrency"
|
||||
[totalValueInBaseCurrency]="totalValueInBaseCurrency"
|
||||
[transactionCount]="transactionCount"
|
||||
(accountDeleted)="onDeleteAccount($event)"
|
||||
(accountToUpdate)="onUpdateAccount($event)"
|
||||
|
@ -10,6 +10,7 @@ import { prettifySymbol } from '@ghostfolio/common/helper';
|
||||
import {
|
||||
PortfolioDetails,
|
||||
PortfolioPosition,
|
||||
UniqueAsset,
|
||||
User
|
||||
} from '@ghostfolio/common/interfaces';
|
||||
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||
@ -64,7 +65,12 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
|
||||
[name: string]: { name: string; value: number };
|
||||
};
|
||||
public symbols: {
|
||||
[name: string]: { name: string; symbol: string; value: number };
|
||||
[name: string]: {
|
||||
dataSource?: DataSource;
|
||||
name: string;
|
||||
symbol: string;
|
||||
value: number;
|
||||
};
|
||||
};
|
||||
|
||||
public user: User;
|
||||
@ -281,6 +287,7 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
|
||||
|
||||
if (position.assetClass === AssetClass.EQUITY) {
|
||||
this.symbols[prettifySymbol(symbol)] = {
|
||||
dataSource: position.dataSource,
|
||||
name: position.name,
|
||||
symbol: prettifySymbol(symbol),
|
||||
value: aPeriod === 'original' ? position.investment : position.value
|
||||
@ -295,6 +302,14 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
|
||||
this.initializeAnalysisData(this.period);
|
||||
}
|
||||
|
||||
public onProportionChartClicked({ dataSource, symbol }: UniqueAsset) {
|
||||
if (dataSource && symbol) {
|
||||
this.router.navigate([], {
|
||||
queryParams: { dataSource, symbol, positionDetailDialog: true }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public ngOnDestroy() {
|
||||
this.unsubscribeSubject.next();
|
||||
this.unsubscribeSubject.complete();
|
||||
|
@ -89,12 +89,14 @@
|
||||
<mat-card-content>
|
||||
<gf-portfolio-proportion-chart
|
||||
class="mx-auto"
|
||||
cursor="pointer"
|
||||
[baseCurrency]="user?.settings?.baseCurrency"
|
||||
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
|
||||
[keys]="['symbol']"
|
||||
[locale]="user?.settings?.locale"
|
||||
[positions]="symbols"
|
||||
[showLabels]="deviceType !== 'mobile'"
|
||||
(proportionChartClicked)="onProportionChartClicked($event)"
|
||||
></gf-portfolio-proportion-chart>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
|
@ -158,11 +158,11 @@ export class CreateOrUpdateTransactionDialog implements OnDestroy {
|
||||
this.activityForm.controls['type'].disable();
|
||||
}
|
||||
|
||||
if (this.data.activity?.symbol) {
|
||||
if (this.data.activity?.SymbolProfile?.symbol) {
|
||||
this.dataService
|
||||
.fetchSymbolItem({
|
||||
dataSource: this.data.activity?.dataSource,
|
||||
symbol: this.data.activity?.symbol
|
||||
dataSource: this.data.activity?.SymbolProfile?.dataSource,
|
||||
symbol: this.data.activity?.SymbolProfile?.symbol
|
||||
})
|
||||
.pipe(takeUntil(this.unsubscribeSubject))
|
||||
.subscribe(({ marketPrice }) => {
|
||||
@ -196,9 +196,7 @@ export class CreateOrUpdateTransactionDialog implements OnDestroy {
|
||||
} else {
|
||||
this.activityForm.controls['searchSymbol'].setErrors({ incorrect: true });
|
||||
|
||||
this.data.activity.currency = null;
|
||||
this.data.activity.dataSource = null;
|
||||
this.data.activity.symbol = null;
|
||||
this.data.activity.SymbolProfile = null;
|
||||
}
|
||||
|
||||
this.changeDetectorRef.markForCheck();
|
||||
@ -259,9 +257,7 @@ export class CreateOrUpdateTransactionDialog implements OnDestroy {
|
||||
})
|
||||
.pipe(
|
||||
catchError(() => {
|
||||
this.data.activity.currency = null;
|
||||
this.data.activity.dataSource = null;
|
||||
this.data.activity.unitPrice = null;
|
||||
this.data.activity.SymbolProfile = null;
|
||||
|
||||
this.isLoading = false;
|
||||
|
||||
|
@ -3,7 +3,10 @@ import { Injectable } from '@angular/core';
|
||||
import { UpdateMarketDataDto } from '@ghostfolio/api/app/admin/update-market-data.dto';
|
||||
import { IDataProviderHistoricalResponse } from '@ghostfolio/api/services/interfaces/interfaces';
|
||||
import { DATE_FORMAT } from '@ghostfolio/common/helper';
|
||||
import { AdminMarketDataDetails } from '@ghostfolio/common/interfaces';
|
||||
import {
|
||||
AdminMarketDataDetails,
|
||||
UniqueAsset
|
||||
} from '@ghostfolio/common/interfaces';
|
||||
import { DataSource, MarketData } from '@prisma/client';
|
||||
import { format, parseISO } from 'date-fns';
|
||||
import { Observable, map } from 'rxjs';
|
||||
@ -14,13 +17,7 @@ import { Observable, map } from 'rxjs';
|
||||
export class AdminService {
|
||||
public constructor(private http: HttpClient) {}
|
||||
|
||||
public deleteProfileData({
|
||||
dataSource,
|
||||
symbol
|
||||
}: {
|
||||
dataSource: DataSource;
|
||||
symbol: string;
|
||||
}) {
|
||||
public deleteProfileData({ dataSource, symbol }: UniqueAsset) {
|
||||
return this.http.delete<void>(
|
||||
`/api/admin/profile-data/${dataSource}/${symbol}`
|
||||
);
|
||||
@ -53,13 +50,7 @@ export class AdminService {
|
||||
return this.http.post<void>(`/api/admin/gather/profile-data`, {});
|
||||
}
|
||||
|
||||
public gatherProfileDataBySymbol({
|
||||
dataSource,
|
||||
symbol
|
||||
}: {
|
||||
dataSource: DataSource;
|
||||
symbol: string;
|
||||
}) {
|
||||
public gatherProfileDataBySymbol({ dataSource, symbol }: UniqueAsset) {
|
||||
return this.http.post<void>(
|
||||
`/api/admin/gather/profile-data/${dataSource}/${symbol}`,
|
||||
{}
|
||||
@ -70,10 +61,8 @@ export class AdminService {
|
||||
dataSource,
|
||||
date,
|
||||
symbol
|
||||
}: {
|
||||
dataSource: DataSource;
|
||||
}: UniqueAsset & {
|
||||
date?: Date;
|
||||
symbol: string;
|
||||
}) {
|
||||
let url = `/api/admin/gather/${dataSource}/${symbol}`;
|
||||
|
||||
|
@ -24,9 +24,11 @@ import {
|
||||
PortfolioDetails,
|
||||
PortfolioInvestments,
|
||||
PortfolioPerformance,
|
||||
PortfolioPerformanceResponse,
|
||||
PortfolioPublicDetails,
|
||||
PortfolioReport,
|
||||
PortfolioSummary,
|
||||
UniqueAsset,
|
||||
User
|
||||
} from '@ghostfolio/common/interfaces';
|
||||
import { permissions } from '@ghostfolio/common/permissions';
|
||||
@ -188,13 +190,13 @@ export class DataService {
|
||||
});
|
||||
}
|
||||
|
||||
public fetchPortfolioPerformance(aParams: { [param: string]: any }) {
|
||||
return this.http.get<{
|
||||
hasErrors: boolean;
|
||||
performance: PortfolioPerformance;
|
||||
}>('/api/portfolio/performance', {
|
||||
params: aParams
|
||||
});
|
||||
public fetchPortfolioPerformance(params: { [param: string]: any }) {
|
||||
return this.http.get<PortfolioPerformanceResponse>(
|
||||
'/api/portfolio/performance',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public fetchPortfolioPublic(aId: string) {
|
||||
|
@ -134,6 +134,10 @@ ngx-skeleton-loader {
|
||||
}
|
||||
}
|
||||
|
||||
.cursor-default {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
@ -42,6 +42,8 @@ export const warnColorRgb = {
|
||||
b: 69
|
||||
};
|
||||
|
||||
export const ASSET_SUB_CLASS_EMERGENCY_FUND = 'EMERGENCY_FUND';
|
||||
|
||||
export const DEFAULT_DATE_FORMAT = 'dd.MM.yyyy';
|
||||
export const DEFAULT_DATE_FORMAT_MONTH_YEAR = 'MMM yyyy';
|
||||
|
||||
|
@ -102,10 +102,6 @@ export function isCurrency(aSymbol = '') {
|
||||
return currencies[aSymbol];
|
||||
}
|
||||
|
||||
export function isGhostfolioScraperApiSymbol(aSymbol = '') {
|
||||
return aSymbol.startsWith(ghostfolioScraperApiSymbolPrefix);
|
||||
}
|
||||
|
||||
export function resetHours(aDate: Date) {
|
||||
const year = getYear(aDate);
|
||||
const month = getMonth(aDate);
|
||||
|
@ -2,7 +2,7 @@ import { AccountWithValue } from '@ghostfolio/common/types';
|
||||
|
||||
export interface Accounts {
|
||||
accounts: AccountWithValue[];
|
||||
totalBalance: number;
|
||||
totalValue: number;
|
||||
totalBalanceInBaseCurrency: number;
|
||||
totalValueInBaseCurrency: number;
|
||||
transactionCount: number;
|
||||
}
|
||||
|
@ -1,5 +1,3 @@
|
||||
import { Property } from '@prisma/client';
|
||||
|
||||
export interface AdminData {
|
||||
dataGatheringProgress?: number;
|
||||
exchangeRates: { label1: string; label2: string; value: number }[];
|
||||
|
@ -1,3 +1,6 @@
|
||||
import { StringValue } from 'ms';
|
||||
|
||||
export interface Coupon {
|
||||
code: string;
|
||||
duration?: StringValue;
|
||||
}
|
||||
|
@ -21,7 +21,10 @@ import { PortfolioReportRule } from './portfolio-report-rule.interface';
|
||||
import { PortfolioReport } from './portfolio-report.interface';
|
||||
import { PortfolioSummary } from './portfolio-summary.interface';
|
||||
import { Position } from './position.interface';
|
||||
import { ResponseError } from './responses/errors.interface';
|
||||
import { PortfolioPerformanceResponse } from './responses/portfolio-performance-response.interface';
|
||||
import { TimelinePosition } from './timeline-position.interface';
|
||||
import { UniqueAsset } from './unique-asset.interface';
|
||||
import { UserSettings } from './user-settings.interface';
|
||||
import { UserWithSettings } from './user-with-settings';
|
||||
import { User } from './user.interface';
|
||||
@ -42,13 +45,16 @@ export {
|
||||
PortfolioItem,
|
||||
PortfolioOverview,
|
||||
PortfolioPerformance,
|
||||
PortfolioPerformanceResponse,
|
||||
PortfolioPosition,
|
||||
PortfolioPublicDetails,
|
||||
PortfolioReport,
|
||||
PortfolioReportRule,
|
||||
PortfolioSummary,
|
||||
Position,
|
||||
ResponseError,
|
||||
TimelinePosition,
|
||||
UniqueAsset,
|
||||
User,
|
||||
UserSettings,
|
||||
UserWithSettings
|
||||
|
@ -8,7 +8,7 @@ export interface PortfolioPosition {
|
||||
allocationCurrent: number;
|
||||
allocationInvestment: number;
|
||||
assetClass?: AssetClass;
|
||||
assetSubClass?: AssetSubClass | 'CASH';
|
||||
assetSubClass?: AssetSubClass | 'CASH' | 'EMERGENCY_FUND';
|
||||
countries: Country[];
|
||||
currency: string;
|
||||
dataSource: DataSource;
|
||||
|
@ -5,6 +5,7 @@ export interface PortfolioSummary extends PortfolioPerformance {
|
||||
cash: number;
|
||||
dividend: number;
|
||||
committedFunds: number;
|
||||
emergencyFund: number;
|
||||
fees: number;
|
||||
firstOrderDate: Date;
|
||||
items: number;
|
||||
|
@ -0,0 +1,6 @@
|
||||
import { UniqueAsset } from '../unique-asset.interface';
|
||||
|
||||
export interface ResponseError {
|
||||
errors?: UniqueAsset[];
|
||||
hasErrors: boolean;
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
import { PortfolioPerformance } from '../portfolio-performance.interface';
|
||||
import { ResponseError } from './errors.interface';
|
||||
|
||||
export interface PortfolioPerformanceResponse extends ResponseError {
|
||||
performance: PortfolioPerformance;
|
||||
}
|
6
libs/common/src/lib/interfaces/unique-asset.interface.ts
Normal file
6
libs/common/src/lib/interfaces/unique-asset.interface.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { DataSource } from '@prisma/client';
|
||||
|
||||
export interface UniqueAsset {
|
||||
dataSource: DataSource;
|
||||
symbol: string;
|
||||
}
|
@ -1,7 +1,8 @@
|
||||
import { Account as AccountModel } from '@prisma/client';
|
||||
|
||||
export type AccountWithValue = AccountModel & {
|
||||
convertedBalance: number;
|
||||
balanceInBaseCurrency: number;
|
||||
transactionCount: number;
|
||||
value: number;
|
||||
valueInBaseCurrency: number;
|
||||
};
|
||||
|
@ -144,7 +144,7 @@
|
||||
class="d-none d-lg-table-cell px-1"
|
||||
mat-cell
|
||||
>
|
||||
{{ element.currency }}
|
||||
{{ element.SymbolProfile.currency }}
|
||||
</td>
|
||||
<td *matFooterCellDef class="d-none d-lg-table-cell px-1" mat-footer-cell>
|
||||
{{ baseCurrency }}
|
||||
@ -362,7 +362,7 @@
|
||||
!row.isDraft &&
|
||||
row.type !== 'ITEM' &&
|
||||
onOpenPositionDialog({
|
||||
dataSource: row.dataSource,
|
||||
dataSource: row.SymbolProfile.dataSource,
|
||||
symbol: row.SymbolProfile.symbol
|
||||
})
|
||||
"
|
||||
|
@ -21,6 +21,7 @@ import { MatTableDataSource } from '@angular/material/table';
|
||||
import { Router } from '@angular/router';
|
||||
import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface';
|
||||
import { DEFAULT_DATE_FORMAT } from '@ghostfolio/common/config';
|
||||
import { UniqueAsset } from '@ghostfolio/common/interfaces';
|
||||
import { OrderWithAccount } from '@ghostfolio/common/types';
|
||||
import { DataSource } from '@prisma/client';
|
||||
import Big from 'big.js';
|
||||
@ -199,13 +200,7 @@ export class ActivitiesTableComponent implements OnChanges, OnDestroy {
|
||||
this.import.emit();
|
||||
}
|
||||
|
||||
public onOpenPositionDialog({
|
||||
dataSource,
|
||||
symbol
|
||||
}: {
|
||||
dataSource: DataSource;
|
||||
symbol: string;
|
||||
}): void {
|
||||
public onOpenPositionDialog({ dataSource, symbol }: UniqueAsset): void {
|
||||
this.router.navigate([], {
|
||||
queryParams: { dataSource, symbol, positionDetailDialog: true }
|
||||
});
|
||||
|
@ -3,14 +3,17 @@ import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
ElementRef,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnDestroy,
|
||||
Output,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
import { UNKNOWN_KEY } from '@ghostfolio/common/config';
|
||||
import { getTextColor } from '@ghostfolio/common/helper';
|
||||
import { PortfolioPosition } from '@ghostfolio/common/interfaces';
|
||||
import { PortfolioPosition, UniqueAsset } from '@ghostfolio/common/interfaces';
|
||||
import { DataSource } from '@prisma/client';
|
||||
import Big from 'big.js';
|
||||
import { Tooltip } from 'chart.js';
|
||||
import { LinearScale } from 'chart.js';
|
||||
@ -30,6 +33,7 @@ export class PortfolioProportionChartComponent
|
||||
implements AfterViewInit, OnChanges, OnDestroy
|
||||
{
|
||||
@Input() baseCurrency: string;
|
||||
@Input() cursor: string;
|
||||
@Input() isInPercent = false;
|
||||
@Input() keys: string[] = [];
|
||||
@Input() locale = '';
|
||||
@ -37,11 +41,14 @@ export class PortfolioProportionChartComponent
|
||||
@Input() showLabels = false;
|
||||
@Input() positions: {
|
||||
[symbol: string]: Pick<PortfolioPosition, 'type'> & {
|
||||
dataSource?: DataSource;
|
||||
name: string;
|
||||
value: number;
|
||||
};
|
||||
} = {};
|
||||
|
||||
@Output() proportionChartClicked = new EventEmitter<UniqueAsset>();
|
||||
|
||||
@ViewChild('chartCanvas') chartCanvas: ElementRef<HTMLCanvasElement>;
|
||||
|
||||
public chart: Chart;
|
||||
@ -115,7 +122,7 @@ export class PortfolioProportionChartComponent
|
||||
chartData[this.positions[symbol][this.keys[0]]] = {
|
||||
name: this.positions[symbol].name,
|
||||
subCategory: {},
|
||||
value: new Big(this.positions[symbol].value)
|
||||
value: new Big(this.positions[symbol].value ?? 0)
|
||||
};
|
||||
|
||||
if (this.positions[symbol][this.keys[1]]) {
|
||||
@ -256,6 +263,21 @@ export class PortfolioProportionChartComponent
|
||||
layout: {
|
||||
padding: this.showLabels === true ? 100 : 0
|
||||
},
|
||||
onClick: (event, activeElements) => {
|
||||
const dataIndex = activeElements[0].index;
|
||||
const symbol: string = event.chart.data.labels[dataIndex];
|
||||
|
||||
const dataSource = this.positions[symbol]?.dataSource;
|
||||
|
||||
this.proportionChartClicked.emit({ dataSource, symbol });
|
||||
},
|
||||
onHover: (event, chartElement) => {
|
||||
if (this.cursor) {
|
||||
event.native.target.style.cursor = chartElement[0]
|
||||
? this.cursor
|
||||
: 'default';
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
datalabels: {
|
||||
color: (context) => {
|
||||
|
33
package.json
33
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ghostfolio",
|
||||
"version": "1.121.0",
|
||||
"version": "1.125.0",
|
||||
"homepage": "https://ghostfol.io",
|
||||
"license": "AGPL-3.0",
|
||||
"scripts": {
|
||||
@ -70,8 +70,8 @@
|
||||
"@nestjs/platform-express": "8.2.3",
|
||||
"@nestjs/schedule": "1.0.2",
|
||||
"@nestjs/serve-static": "2.2.2",
|
||||
"@nrwl/angular": "13.8.1",
|
||||
"@prisma/client": "3.9.1",
|
||||
"@nrwl/angular": "13.8.5",
|
||||
"@prisma/client": "3.10.0",
|
||||
"@simplewebauthn/browser": "4.1.0",
|
||||
"@simplewebauthn/server": "4.1.0",
|
||||
"@simplewebauthn/typescript-types": "4.0.0",
|
||||
@ -100,15 +100,16 @@
|
||||
"http-status-codes": "2.2.0",
|
||||
"ionicons": "5.5.1",
|
||||
"lodash": "4.17.21",
|
||||
"ms": "3.0.0-canary.1",
|
||||
"ngx-device-detector": "3.0.0",
|
||||
"ngx-markdown": "13.0.0",
|
||||
"ngx-skeleton-loader": "2.9.1",
|
||||
"ngx-skeleton-loader": "5.0.0",
|
||||
"ngx-stripe": "13.0.0",
|
||||
"papaparse": "5.3.1",
|
||||
"passport": "0.4.1",
|
||||
"passport-google-oauth20": "2.0.0",
|
||||
"passport-jwt": "4.0.0",
|
||||
"prisma": "3.9.1",
|
||||
"prisma": "3.10.0",
|
||||
"reflect-metadata": "0.1.13",
|
||||
"round-to": "5.0.0",
|
||||
"rxjs": "7.4.0",
|
||||
@ -117,7 +118,7 @@
|
||||
"tslib": "2.0.0",
|
||||
"twitter-api-v2": "1.10.3",
|
||||
"uuid": "8.3.2",
|
||||
"yahoo-finance2": "2.1.9",
|
||||
"yahoo-finance2": "2.2.0",
|
||||
"zone.js": "0.11.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -131,15 +132,15 @@
|
||||
"@angular/localize": "13.2.2",
|
||||
"@nestjs/schematics": "8.0.5",
|
||||
"@nestjs/testing": "8.2.3",
|
||||
"@nrwl/cli": "13.8.1",
|
||||
"@nrwl/cypress": "13.8.1",
|
||||
"@nrwl/eslint-plugin-nx": "13.8.1",
|
||||
"@nrwl/jest": "13.8.1",
|
||||
"@nrwl/nest": "13.8.1",
|
||||
"@nrwl/node": "13.8.1",
|
||||
"@nrwl/storybook": "13.8.1",
|
||||
"@nrwl/tao": "13.8.1",
|
||||
"@nrwl/workspace": "13.8.1",
|
||||
"@nrwl/cli": "13.8.5",
|
||||
"@nrwl/cypress": "13.8.5",
|
||||
"@nrwl/eslint-plugin-nx": "13.8.5",
|
||||
"@nrwl/jest": "13.8.5",
|
||||
"@nrwl/nest": "13.8.5",
|
||||
"@nrwl/node": "13.8.5",
|
||||
"@nrwl/storybook": "13.8.5",
|
||||
"@nrwl/tao": "13.8.5",
|
||||
"@nrwl/workspace": "13.8.5",
|
||||
"@storybook/addon-essentials": "6.4.18",
|
||||
"@storybook/angular": "6.4.18",
|
||||
"@storybook/builder-webpack5": "6.4.18",
|
||||
@ -165,7 +166,7 @@
|
||||
"import-sort-parser-typescript": "6.0.0",
|
||||
"import-sort-style-module": "6.0.0",
|
||||
"jest": "27.2.3",
|
||||
"jest-preset-angular": "11.0.0",
|
||||
"jest-preset-angular": "11.1.1",
|
||||
"prettier": "2.5.1",
|
||||
"replace-in-file": "6.2.0",
|
||||
"rimraf": "3.0.2",
|
||||
|
@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Order" DROP COLUMN "dataSource";
|
@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Order" DROP COLUMN "currency";
|
@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Order" DROP COLUMN "symbol";
|
@ -0,0 +1,5 @@
|
||||
-- Set default value
|
||||
UPDATE "SymbolProfile" SET "currency" = 'USD' WHERE "currency" IS NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "SymbolProfile" ALTER COLUMN "currency" SET NOT NULL;
|
@ -74,14 +74,11 @@ model Order {
|
||||
accountId String?
|
||||
accountUserId String?
|
||||
createdAt DateTime @default(now())
|
||||
currency String?
|
||||
dataSource DataSource?
|
||||
date DateTime
|
||||
fee Float
|
||||
id String @default(uuid())
|
||||
isDraft Boolean @default(false)
|
||||
quantity Float
|
||||
symbol String?
|
||||
SymbolProfile SymbolProfile @relation(fields: [symbolProfileId], references: [id])
|
||||
symbolProfileId String
|
||||
type Type
|
||||
@ -119,7 +116,7 @@ model SymbolProfile {
|
||||
assetSubClass AssetSubClass?
|
||||
countries Json?
|
||||
createdAt DateTime @default(now())
|
||||
currency String?
|
||||
currency String
|
||||
dataSource DataSource
|
||||
id String @id @default(uuid())
|
||||
name String?
|
||||
|
@ -192,14 +192,11 @@ async function main() {
|
||||
{
|
||||
accountId: '65cfb79d-b6c7-4591-9d46-73426bc62094',
|
||||
accountUserId: userDemo.id,
|
||||
currency: 'USD',
|
||||
dataSource: DataSource.YAHOO,
|
||||
date: new Date(Date.UTC(2017, 0, 3, 0, 0, 0)),
|
||||
fee: 30,
|
||||
id: 'cf7c0418-8535-4089-ae3d-5dbfa0aec2e1',
|
||||
quantity: 50,
|
||||
symbol: 'TSLA',
|
||||
symbolProfileId: 'd1ee9681-fb21-4f99-a3b7-afd4fc04df2e',
|
||||
symbolProfileId: 'd1ee9681-fb21-4f99-a3b7-afd4fc04df2e', // TSLA
|
||||
type: Type.BUY,
|
||||
unitPrice: 42.97,
|
||||
userId: userDemo.id
|
||||
@ -207,14 +204,11 @@ async function main() {
|
||||
{
|
||||
accountId: 'd804de69-0429-42dc-b6ca-b308fd7dd926',
|
||||
accountUserId: userDemo.id,
|
||||
currency: 'USD',
|
||||
dataSource: DataSource.YAHOO,
|
||||
date: new Date(Date.UTC(2017, 7, 16, 0, 0, 0)),
|
||||
fee: 29.9,
|
||||
id: 'a1c5d73a-8631-44e5-ac44-356827a5212c',
|
||||
quantity: 0.5614682,
|
||||
symbol: 'BTCUSD',
|
||||
symbolProfileId: 'fdc42ea6-1321-44f5-9fb0-d7f1f2cf9b1e',
|
||||
symbolProfileId: 'fdc42ea6-1321-44f5-9fb0-d7f1f2cf9b1e', // BTCUSD
|
||||
type: Type.BUY,
|
||||
unitPrice: 3562.089535970158,
|
||||
userId: userDemo.id
|
||||
@ -222,14 +216,11 @@ async function main() {
|
||||
{
|
||||
accountId: '480269ce-e12a-4fd1-ac88-c4b0ff3f899c',
|
||||
accountUserId: userDemo.id,
|
||||
currency: 'USD',
|
||||
dataSource: DataSource.YAHOO,
|
||||
date: new Date(Date.UTC(2018, 9, 1, 0, 0, 0)),
|
||||
fee: 80.79,
|
||||
id: '71c08e2a-4a86-44ae-a890-c337de5d5f9b',
|
||||
quantity: 5,
|
||||
symbol: 'AMZN',
|
||||
symbolProfileId: '2bd26362-136e-411c-b578-334084b4cdcc',
|
||||
symbolProfileId: '2bd26362-136e-411c-b578-334084b4cdcc', // AMZN
|
||||
type: Type.BUY,
|
||||
unitPrice: 2021.99,
|
||||
userId: userDemo.id
|
||||
@ -237,14 +228,11 @@ async function main() {
|
||||
{
|
||||
accountId: '480269ce-e12a-4fd1-ac88-c4b0ff3f899c',
|
||||
accountUserId: userDemo.id,
|
||||
currency: 'USD',
|
||||
dataSource: DataSource.YAHOO,
|
||||
date: new Date(Date.UTC(2019, 2, 1, 0, 0, 0)),
|
||||
fee: 19.9,
|
||||
id: '385f2c2c-d53e-4937-b0e5-e92ef6020d4e',
|
||||
quantity: 10,
|
||||
symbol: 'VTI',
|
||||
symbolProfileId: '7d9c8540-061e-4e7e-b019-0d0f4a84e796',
|
||||
symbolProfileId: '7d9c8540-061e-4e7e-b019-0d0f4a84e796', // VTI
|
||||
type: Type.BUY,
|
||||
unitPrice: 144.38,
|
||||
userId: userDemo.id
|
||||
@ -252,14 +240,11 @@ async function main() {
|
||||
{
|
||||
accountId: '480269ce-e12a-4fd1-ac88-c4b0ff3f899c',
|
||||
accountUserId: userDemo.id,
|
||||
currency: 'USD',
|
||||
dataSource: DataSource.YAHOO,
|
||||
date: new Date(Date.UTC(2019, 8, 3, 0, 0, 0)),
|
||||
fee: 19.9,
|
||||
id: '185f2c2c-d53e-4937-b0e5-a93ef6020d4e',
|
||||
quantity: 10,
|
||||
symbol: 'VTI',
|
||||
symbolProfileId: '7d9c8540-061e-4e7e-b019-0d0f4a84e796',
|
||||
symbolProfileId: '7d9c8540-061e-4e7e-b019-0d0f4a84e796', // VTI
|
||||
type: Type.BUY,
|
||||
unitPrice: 147.99,
|
||||
userId: userDemo.id
|
||||
@ -267,14 +252,11 @@ async function main() {
|
||||
{
|
||||
accountId: '480269ce-e12a-4fd1-ac88-c4b0ff3f899c',
|
||||
accountUserId: userDemo.id,
|
||||
currency: 'USD',
|
||||
dataSource: DataSource.YAHOO,
|
||||
date: new Date(Date.UTC(2020, 2, 2, 0, 0, 0)),
|
||||
fee: 19.9,
|
||||
id: '347b0430-a84f-4031-a0f9-390399066ad6',
|
||||
quantity: 10,
|
||||
symbol: 'VTI',
|
||||
symbolProfileId: '7d9c8540-061e-4e7e-b019-0d0f4a84e796',
|
||||
symbolProfileId: '7d9c8540-061e-4e7e-b019-0d0f4a84e796', // VTI
|
||||
type: Type.BUY,
|
||||
unitPrice: 151.41,
|
||||
userId: userDemo.id
|
||||
@ -282,14 +264,11 @@ async function main() {
|
||||
{
|
||||
accountId: '480269ce-e12a-4fd1-ac88-c4b0ff3f899c',
|
||||
accountUserId: userDemo.id,
|
||||
currency: 'USD',
|
||||
dataSource: DataSource.YAHOO,
|
||||
date: new Date(Date.UTC(2020, 8, 1, 0, 0, 0)),
|
||||
fee: 19.9,
|
||||
id: '67ec3f47-3189-4b63-ba05-60d3a06b302f',
|
||||
quantity: 10,
|
||||
symbol: 'VTI',
|
||||
symbolProfileId: '7d9c8540-061e-4e7e-b019-0d0f4a84e796',
|
||||
symbolProfileId: '7d9c8540-061e-4e7e-b019-0d0f4a84e796', // VTI
|
||||
type: Type.BUY,
|
||||
unitPrice: 177.69,
|
||||
userId: userDemo.id
|
||||
@ -297,14 +276,11 @@ async function main() {
|
||||
{
|
||||
accountId: '480269ce-e12a-4fd1-ac88-c4b0ff3f899c',
|
||||
accountUserId: userDemo.id,
|
||||
currency: 'USD',
|
||||
dataSource: DataSource.YAHOO,
|
||||
date: new Date(Date.UTC(2020, 2, 1, 0, 0, 0)),
|
||||
fee: 19.9,
|
||||
id: 'd01c6fbc-fa8d-47e6-8e80-66f882d2bfd2',
|
||||
quantity: 10,
|
||||
symbol: 'VTI',
|
||||
symbolProfileId: '7d9c8540-061e-4e7e-b019-0d0f4a84e796',
|
||||
symbolProfileId: '7d9c8540-061e-4e7e-b019-0d0f4a84e796', // VTI
|
||||
type: Type.BUY,
|
||||
unitPrice: 203.15,
|
||||
userId: userDemo.id
|
||||
|
556
yarn.lock
556
yarn.lock
@ -3196,17 +3196,17 @@
|
||||
node-gyp "^8.2.0"
|
||||
read-package-json-fast "^2.0.1"
|
||||
|
||||
"@nrwl/angular@13.8.1":
|
||||
version "13.8.1"
|
||||
resolved "https://registry.yarnpkg.com/@nrwl/angular/-/angular-13.8.1.tgz#081fcb7b7a94f15c3e52cc999cc55794ecb6553a"
|
||||
integrity sha512-irKPeIkBvK2HVivwyamqNC1dMnV/dI1hup6y6pFsYDCygSBX8PWjZSXTLXEik9uviGwn+qOgEl7YTcxIOfKoag==
|
||||
"@nrwl/angular@13.8.5":
|
||||
version "13.8.5"
|
||||
resolved "https://registry.yarnpkg.com/@nrwl/angular/-/angular-13.8.5.tgz#c9f585a08be22d4b94e54f36bd85b34fae24c180"
|
||||
integrity sha512-S+BjdVHW/VuTPVWkWztkefQjMzikF3hF5wiN59s7wPeSkE+FjXj7YEdpUuR58/0W23gR0ao8eVisYriZaPvq8Q==
|
||||
dependencies:
|
||||
"@angular-devkit/schematics" "~13.2.0"
|
||||
"@nrwl/cypress" "13.8.1"
|
||||
"@nrwl/devkit" "13.8.1"
|
||||
"@nrwl/jest" "13.8.1"
|
||||
"@nrwl/linter" "13.8.1"
|
||||
"@nrwl/storybook" "13.8.1"
|
||||
"@nrwl/cypress" "13.8.5"
|
||||
"@nrwl/devkit" "13.8.5"
|
||||
"@nrwl/jest" "13.8.5"
|
||||
"@nrwl/linter" "13.8.5"
|
||||
"@nrwl/storybook" "13.8.5"
|
||||
"@phenomnomnominal/tsquery" "4.1.1"
|
||||
"@schematics/angular" "~13.2.0"
|
||||
ignore "^5.0.4"
|
||||
@ -3218,26 +3218,26 @@
|
||||
tslib "^2.3.0"
|
||||
webpack-merge "5.7.3"
|
||||
|
||||
"@nrwl/cli@13.8.1":
|
||||
version "13.8.1"
|
||||
resolved "https://registry.yarnpkg.com/@nrwl/cli/-/cli-13.8.1.tgz#31af91b27f4c19e736dd9793b0f36f69ef482256"
|
||||
integrity sha512-oQtu0rkpEm3QdzqB/BCDsOl0OJ5P2afSfzu3Lxcrz6fHjmUf9aby0sd1JCrRNRrZkxK8GAdxRKZdPHkdWvr23A==
|
||||
"@nrwl/cli@13.8.5":
|
||||
version "13.8.5"
|
||||
resolved "https://registry.yarnpkg.com/@nrwl/cli/-/cli-13.8.5.tgz#df9ca6f8841965195296e1642126ebcd77e204af"
|
||||
integrity sha512-vxDZUCl1u2ZGZATyxBCAzMlR1cLnNwZMzl8yAW2ghnzWun5QynYeOg6GfcoE232E2rIov9YDbEeh2ZusMJeYuw==
|
||||
dependencies:
|
||||
"@nrwl/tao" "13.8.1"
|
||||
"@nrwl/tao" "13.8.5"
|
||||
chalk "4.1.0"
|
||||
enquirer "~2.3.6"
|
||||
v8-compile-cache "2.3.0"
|
||||
yargs-parser "20.0.0"
|
||||
|
||||
"@nrwl/cypress@13.8.1":
|
||||
version "13.8.1"
|
||||
resolved "https://registry.yarnpkg.com/@nrwl/cypress/-/cypress-13.8.1.tgz#e46de921a4b97862ce5756f55deec72fa955ed58"
|
||||
integrity sha512-i4JAEZPCG/jPrDUmiWA3nBVICcCa+ZN4T4WcRGJrOVxLfa4IPfEJbdAW73Dh/ddDHQ47mN1x6DSDdNbthdmaQQ==
|
||||
"@nrwl/cypress@13.8.5":
|
||||
version "13.8.5"
|
||||
resolved "https://registry.yarnpkg.com/@nrwl/cypress/-/cypress-13.8.5.tgz#ced128ede06ce1496aef1b0a2fbcf795606e18fd"
|
||||
integrity sha512-D57S5EeUzW6ZmW+LSaRj47+uyKOwC0PQAYL5CP1SXkUDgUu+jh1o3glASPXbtfqFMXjlWk1Mo9eDEPxw9p814g==
|
||||
dependencies:
|
||||
"@cypress/webpack-preprocessor" "^5.9.1"
|
||||
"@nrwl/devkit" "13.8.1"
|
||||
"@nrwl/linter" "13.8.1"
|
||||
"@nrwl/workspace" "13.8.1"
|
||||
"@nrwl/devkit" "13.8.5"
|
||||
"@nrwl/linter" "13.8.5"
|
||||
"@nrwl/workspace" "13.8.5"
|
||||
chalk "4.1.0"
|
||||
enhanced-resolve "^5.8.3"
|
||||
fork-ts-checker-webpack-plugin "6.2.10"
|
||||
@ -3248,44 +3248,37 @@
|
||||
tslib "^2.3.0"
|
||||
webpack-node-externals "^3.0.0"
|
||||
|
||||
"@nrwl/devkit@13.8.1":
|
||||
version "13.8.1"
|
||||
resolved "https://registry.yarnpkg.com/@nrwl/devkit/-/devkit-13.8.1.tgz#03184e057b04b2a451dd7856e0e8008b8def1685"
|
||||
integrity sha512-zznDaYf6yTBbr8xOb8l4Dn7L0QhCS7BMUoCq/PMCBLwRnRBDpbd801tD06qIVvhh3XkwEJVS2v7EEF3TOypIyw==
|
||||
"@nrwl/devkit@13.8.5":
|
||||
version "13.8.5"
|
||||
resolved "https://registry.yarnpkg.com/@nrwl/devkit/-/devkit-13.8.5.tgz#f5cc8de7a66778b1763412b07ca3cf6e4039de3a"
|
||||
integrity sha512-WSxK3sSVCU4+BIgARfe5dJvNn1xkLyjuIPilpOz7TTQffF3GZ1okGIik+sVHuumgbYodK7gVWihCyt/7+t4xig==
|
||||
dependencies:
|
||||
"@nrwl/tao" "13.8.1"
|
||||
"@nrwl/tao" "13.8.5"
|
||||
ejs "^3.1.5"
|
||||
ignore "^5.0.4"
|
||||
rxjs "^6.5.4"
|
||||
semver "7.3.4"
|
||||
tslib "^2.3.0"
|
||||
|
||||
"@nrwl/eslint-plugin-nx@13.8.1":
|
||||
version "13.8.1"
|
||||
resolved "https://registry.yarnpkg.com/@nrwl/eslint-plugin-nx/-/eslint-plugin-nx-13.8.1.tgz#6a5c045d0b95f63a2adbd07cfbdaa62045e7c9bd"
|
||||
integrity sha512-kFuimLFKJXhaJU447fn6UTldfdQy5trjkvxVqNx8lc8Ole25E+ERb+eU239HijTR3YakfzyHN9ffdDguyp1f7w==
|
||||
"@nrwl/eslint-plugin-nx@13.8.5":
|
||||
version "13.8.5"
|
||||
resolved "https://registry.yarnpkg.com/@nrwl/eslint-plugin-nx/-/eslint-plugin-nx-13.8.5.tgz#a9eaaa7f3db49319e5ef6fb25b3c37f051a0b03d"
|
||||
integrity sha512-M/UvJIxyGW/e6Yj3pKrjT6GSibJXasBMy9YbwuvlmWXMHUfm3wUULPeyglxELvMhwNmE8pJAhh8a8bedDQeTfQ==
|
||||
dependencies:
|
||||
"@nrwl/devkit" "13.8.1"
|
||||
"@nrwl/workspace" "13.8.1"
|
||||
"@swc-node/register" "^1.4.2"
|
||||
"@nrwl/devkit" "13.8.5"
|
||||
"@nrwl/workspace" "13.8.5"
|
||||
"@typescript-eslint/experimental-utils" "~5.10.0"
|
||||
chalk "4.1.0"
|
||||
confusing-browser-globals "^1.0.9"
|
||||
tsconfig-paths "^3.9.0"
|
||||
optionalDependencies:
|
||||
"@swc/core-linux-arm64-gnu" "^1.2.136"
|
||||
"@swc/core-linux-arm64-musl" "^1.2.136"
|
||||
"@swc/core-linux-x64-gnu" "^1.2.136"
|
||||
"@swc/core-linux-x64-musl" "^1.2.136"
|
||||
|
||||
"@nrwl/jest@13.8.1":
|
||||
version "13.8.1"
|
||||
resolved "https://registry.yarnpkg.com/@nrwl/jest/-/jest-13.8.1.tgz#32f2c9c28ae03e0f4a5bdd8fc6688c4bbca8ab09"
|
||||
integrity sha512-kY6/Fg3aFODVk250qWcJPJWO+pDUN6VFOAUEz03sxkmkfZEA8MRG0xgQrYl9dXcLDK1apoEGJ4sGZ2r8QpA7AA==
|
||||
"@nrwl/jest@13.8.5":
|
||||
version "13.8.5"
|
||||
resolved "https://registry.yarnpkg.com/@nrwl/jest/-/jest-13.8.5.tgz#9d6645d6efc2c64fd67110fb7485d79cd043ec08"
|
||||
integrity sha512-yb4tThYusdBByFlrXp9DAy/Z6f+V9OnEB0CIRK/j8hFipFqQyMPIDP2DeMQw/F17DKB1FdaEX3vMEA6xP+V2eg==
|
||||
dependencies:
|
||||
"@jest/reporters" "27.2.2"
|
||||
"@jest/test-result" "27.2.2"
|
||||
"@nrwl/devkit" "13.8.1"
|
||||
"@nrwl/devkit" "13.8.5"
|
||||
chalk "4.1.0"
|
||||
identity-obj-proxy "3.0.0"
|
||||
jest-config "27.2.2"
|
||||
@ -3295,37 +3288,58 @@
|
||||
rxjs "^6.5.4"
|
||||
tslib "^2.3.0"
|
||||
|
||||
"@nrwl/linter@13.8.1":
|
||||
version "13.8.1"
|
||||
resolved "https://registry.yarnpkg.com/@nrwl/linter/-/linter-13.8.1.tgz#ee5c513c9c584ce7861736c574f12dfc0b266bcf"
|
||||
integrity sha512-WBSpWUccaq1skr82VauvdRfjfmrkAXjHFalg72JqeDv0Ou5AhUWHLhEC1lvXZXPFMeFJtUaAEFbkSkOb6U+K2g==
|
||||
"@nrwl/js@13.8.5":
|
||||
version "13.8.5"
|
||||
resolved "https://registry.yarnpkg.com/@nrwl/js/-/js-13.8.5.tgz#9527668f267f29f7410fd326e7b77eaab5650ea4"
|
||||
integrity sha512-qSHmB0pbTbmWwHJRVqr1kWm2nnPgFUCXsTyvkAQiRyUGCRo1jdUM2rRyhwPjgH6JMnhr1HM1L4balfr2hURn7g==
|
||||
dependencies:
|
||||
"@nrwl/devkit" "13.8.1"
|
||||
"@nrwl/jest" "13.8.1"
|
||||
"@nrwl/devkit" "13.8.5"
|
||||
"@nrwl/jest" "13.8.5"
|
||||
"@nrwl/linter" "13.8.5"
|
||||
"@nrwl/workspace" "13.8.5"
|
||||
"@parcel/watcher" "2.0.4"
|
||||
chalk "4.1.0"
|
||||
fast-glob "^3.2.7"
|
||||
fs-extra "^9.1.0"
|
||||
ignore "^5.0.4"
|
||||
js-tokens "^4.0.0"
|
||||
minimatch "3.0.4"
|
||||
source-map-support "0.5.19"
|
||||
tree-kill "1.2.2"
|
||||
|
||||
"@nrwl/linter@13.8.5":
|
||||
version "13.8.5"
|
||||
resolved "https://registry.yarnpkg.com/@nrwl/linter/-/linter-13.8.5.tgz#526539abfe3393c62f6c5f6103a4e6af74571bf7"
|
||||
integrity sha512-9R5yG35liLk8Q8ZtFSF7MKV8cktcG1lAQ2T5JVn4WxELfkrdAHYl/QfQ+R3AYSsdMiGh580sJBZ8875qcOwrYw==
|
||||
dependencies:
|
||||
"@nrwl/devkit" "13.8.5"
|
||||
"@nrwl/jest" "13.8.5"
|
||||
"@phenomnomnominal/tsquery" "4.1.1"
|
||||
tmp "~0.2.1"
|
||||
tslib "^2.3.0"
|
||||
|
||||
"@nrwl/nest@13.8.1":
|
||||
version "13.8.1"
|
||||
resolved "https://registry.yarnpkg.com/@nrwl/nest/-/nest-13.8.1.tgz#1e172452956da908d4f728e73fed58e97372d3d0"
|
||||
integrity sha512-vlYQPyT7NpPJR6YSXm+RdVQu0dbCvbrsyTDNpPTRQiQuz3Q6pcn/fLTaDhfi6I06aGqTzj6bASUJ9oHFVj/5Ww==
|
||||
"@nrwl/nest@13.8.5":
|
||||
version "13.8.5"
|
||||
resolved "https://registry.yarnpkg.com/@nrwl/nest/-/nest-13.8.5.tgz#8ba6e4929ab88192c3697a2849effac4960b5901"
|
||||
integrity sha512-N3xUYxJRPHK/jJIusrh+ryqqqCqQI9xtEobqE838ztjyVGGoXOHBkIU6u4kBQFkVyg5efCLoL7nUBp1CrhkBnA==
|
||||
dependencies:
|
||||
"@nestjs/schematics" "^8.0.0"
|
||||
"@nrwl/devkit" "13.8.1"
|
||||
"@nrwl/jest" "13.8.1"
|
||||
"@nrwl/linter" "13.8.1"
|
||||
"@nrwl/node" "13.8.1"
|
||||
"@nrwl/devkit" "13.8.5"
|
||||
"@nrwl/jest" "13.8.5"
|
||||
"@nrwl/js" "13.8.5"
|
||||
"@nrwl/linter" "13.8.5"
|
||||
"@nrwl/node" "13.8.5"
|
||||
|
||||
"@nrwl/node@13.8.1":
|
||||
version "13.8.1"
|
||||
resolved "https://registry.yarnpkg.com/@nrwl/node/-/node-13.8.1.tgz#78b99b6bafe72b63ad0cf308f2bf0ccd05e0a423"
|
||||
integrity sha512-D1ZjBV1gAr+CIu4h9fWlazAqeFBg1iAtBsVgzszn6iizaw3y66wq7oknZUozP4uALvkFdK2q+qLEwAsGrZBCyg==
|
||||
"@nrwl/node@13.8.5":
|
||||
version "13.8.5"
|
||||
resolved "https://registry.yarnpkg.com/@nrwl/node/-/node-13.8.5.tgz#435a8d42de4eb2577ac48fa8299ac6aaffa7e02a"
|
||||
integrity sha512-W+Sf+pbfSJzvlIs8xNZ5dRjnYBC9UGNEnDPTuLQi+LIVo40c+3pPD1zXWK6YCpMLqakzKlil0xNJqGbEVRlttA==
|
||||
dependencies:
|
||||
"@nrwl/devkit" "13.8.1"
|
||||
"@nrwl/jest" "13.8.1"
|
||||
"@nrwl/linter" "13.8.1"
|
||||
"@nrwl/workspace" "13.8.1"
|
||||
"@nrwl/devkit" "13.8.5"
|
||||
"@nrwl/jest" "13.8.5"
|
||||
"@nrwl/js" "13.8.5"
|
||||
"@nrwl/linter" "13.8.5"
|
||||
"@nrwl/workspace" "13.8.5"
|
||||
chalk "4.1.0"
|
||||
copy-webpack-plugin "^9.0.1"
|
||||
enhanced-resolve "^5.8.3"
|
||||
@ -3347,48 +3361,51 @@
|
||||
webpack-merge "^5.8.0"
|
||||
webpack-node-externals "^3.0.0"
|
||||
|
||||
"@nrwl/storybook@13.8.1":
|
||||
version "13.8.1"
|
||||
resolved "https://registry.yarnpkg.com/@nrwl/storybook/-/storybook-13.8.1.tgz#038e98225b236099b7d8af698ada06e2e53c9642"
|
||||
integrity sha512-eHnaziiq87Pl2jbSq/CbF2FNfW2WPMfD1A8nCtar/9A6ukpT5xYY027e96hu3a816+WdjAIznIK28klK1Tuwuw==
|
||||
"@nrwl/storybook@13.8.5":
|
||||
version "13.8.5"
|
||||
resolved "https://registry.yarnpkg.com/@nrwl/storybook/-/storybook-13.8.5.tgz#81915a707619b9eab36d17fe29f922a209d25a74"
|
||||
integrity sha512-XAiNSxaRo7ZDM6sZx5wD0eBxWD7oikMxGUqLTC6sEhTdYoWOouepRDbVgOf5qHHZD7TSV9rdIU0vYVIhEbW66g==
|
||||
dependencies:
|
||||
"@nrwl/cypress" "13.8.1"
|
||||
"@nrwl/devkit" "13.8.1"
|
||||
"@nrwl/linter" "13.8.1"
|
||||
"@nrwl/workspace" "13.8.1"
|
||||
"@nrwl/cypress" "13.8.5"
|
||||
"@nrwl/devkit" "13.8.5"
|
||||
"@nrwl/linter" "13.8.5"
|
||||
"@nrwl/workspace" "13.8.5"
|
||||
core-js "^3.6.5"
|
||||
semver "7.3.4"
|
||||
ts-loader "^9.2.6"
|
||||
tsconfig-paths-webpack-plugin "3.5.2"
|
||||
|
||||
"@nrwl/tao@13.8.1":
|
||||
version "13.8.1"
|
||||
resolved "https://registry.yarnpkg.com/@nrwl/tao/-/tao-13.8.1.tgz#6d8168d5cb81ffc1e3e74352db4f5eef7e5ba3f0"
|
||||
integrity sha512-eY05o0napek5b99DH+dir32q2pCemWmwF4ooimU4BnuY90lXC6FUXuB4+w8/tTGTI5TqjfXOnBokTqr3DPDRpQ==
|
||||
"@nrwl/tao@13.8.5":
|
||||
version "13.8.5"
|
||||
resolved "https://registry.yarnpkg.com/@nrwl/tao/-/tao-13.8.5.tgz#223e93dbfe11b47c4c13a66cc9086c2f2572b1ae"
|
||||
integrity sha512-ENT6wpxjSWBYKeLT0YueVFehlN1K2lJzgVOJTk4cQ0LbTw0fJCwcTe4ludiW4hPPTF7P5zzi0PmB9a4ss46tQg==
|
||||
dependencies:
|
||||
"@swc-node/register" "^1.4.2"
|
||||
"@swc/core" "^1.2.146"
|
||||
chalk "4.1.0"
|
||||
enquirer "~2.3.6"
|
||||
fast-glob "3.2.7"
|
||||
fs-extra "^9.1.0"
|
||||
ignore "^5.0.4"
|
||||
jsonc-parser "3.0.0"
|
||||
nx "13.8.1"
|
||||
nx "13.8.5"
|
||||
rxjs "^6.5.4"
|
||||
rxjs-for-await "0.0.2"
|
||||
semver "7.3.4"
|
||||
tmp "~0.2.1"
|
||||
tsconfig-paths "^3.9.0"
|
||||
tslib "^2.3.0"
|
||||
yargs-parser "20.0.0"
|
||||
|
||||
"@nrwl/workspace@13.8.1":
|
||||
version "13.8.1"
|
||||
resolved "https://registry.yarnpkg.com/@nrwl/workspace/-/workspace-13.8.1.tgz#4b27bdd752fdbfd8ca7718a23e204b9129884ac5"
|
||||
integrity sha512-veemewkJtK3UwOGJDcrVw5h+cpjFh3JnmwSnTFHqxKpsN/hkCQk3CgOmBJ4w50qI/gmyuEm+HeGC5/ZNq3kRDA==
|
||||
"@nrwl/workspace@13.8.5":
|
||||
version "13.8.5"
|
||||
resolved "https://registry.yarnpkg.com/@nrwl/workspace/-/workspace-13.8.5.tgz#424a4967ef84be908920a30b83ac5d3a49323347"
|
||||
integrity sha512-uc2IICiSu5hTE1OkVPjBuBlwMl/6zzNL5HnrTCul7dDxRMn0wQsqifTed1QPdgp8Bct6d1uYCc/19fO+wCw1RA==
|
||||
dependencies:
|
||||
"@nrwl/cli" "13.8.1"
|
||||
"@nrwl/devkit" "13.8.1"
|
||||
"@nrwl/jest" "13.8.1"
|
||||
"@nrwl/linter" "13.8.1"
|
||||
"@nrwl/cli" "13.8.5"
|
||||
"@nrwl/devkit" "13.8.5"
|
||||
"@nrwl/jest" "13.8.5"
|
||||
"@nrwl/linter" "13.8.5"
|
||||
"@parcel/watcher" "2.0.4"
|
||||
chalk "4.1.0"
|
||||
chokidar "^3.5.1"
|
||||
@ -3470,22 +3487,22 @@
|
||||
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.10.1.tgz#728ecd95ab207aab8a9a4e421f0422db329232be"
|
||||
integrity sha512-HnUhk1Sy9IuKrxEMdIRCxpIqPw6BFsbYSEUO9p/hNw5sMld/+3OLMWQP80F8/db9qsv3qUjs7ZR5bS/R+iinXw==
|
||||
|
||||
"@prisma/client@3.9.1":
|
||||
version "3.9.1"
|
||||
resolved "https://registry.yarnpkg.com/@prisma/client/-/client-3.9.1.tgz#565c8121f1220637bcab4a1d1f106b8c1334406c"
|
||||
integrity sha512-aLwfXKLvL+loQ0IuPPCXkcq8cXBg1IeoHHa5lqQu3dJHdj45wnislA/Ny4UxRQjD5FXqrfAb8sWtF+jhdmjFTg==
|
||||
"@prisma/client@3.10.0":
|
||||
version "3.10.0"
|
||||
resolved "https://registry.yarnpkg.com/@prisma/client/-/client-3.10.0.tgz#4782fe6f1b0e43c2a11a75ad4bb1098599d1dfb1"
|
||||
integrity sha512-6P4sV7WFuODSfSoSEzCH1qfmWMrCUBk1LIIuTbQf6m1LI/IOpLN4lnqGDmgiBGprEzuWobnGLfe9YsXLn0inrg==
|
||||
dependencies:
|
||||
"@prisma/engines-version" "3.9.0-58.bcc2ff906db47790ee902e7bbc76d7ffb1893009"
|
||||
"@prisma/engines-version" "3.10.0-50.73e60b76d394f8d37d8ebd1f8918c79029f0db86"
|
||||
|
||||
"@prisma/engines-version@3.9.0-58.bcc2ff906db47790ee902e7bbc76d7ffb1893009":
|
||||
version "3.9.0-58.bcc2ff906db47790ee902e7bbc76d7ffb1893009"
|
||||
resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-3.9.0-58.bcc2ff906db47790ee902e7bbc76d7ffb1893009.tgz#ea03ffa723382a526dc6625ce6eae9b6ad984400"
|
||||
integrity sha512-5Dh+qTDhpPR66w6NNAnPs+/W/Qt4r1DSd+qhfPFcDThUK4uxoZKGlPb2IYQn5LL+18aIGnmteDf7BnVMmvBNSQ==
|
||||
"@prisma/engines-version@3.10.0-50.73e60b76d394f8d37d8ebd1f8918c79029f0db86":
|
||||
version "3.10.0-50.73e60b76d394f8d37d8ebd1f8918c79029f0db86"
|
||||
resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-3.10.0-50.73e60b76d394f8d37d8ebd1f8918c79029f0db86.tgz#82750856fa637dd89b8f095d2dcc6ac0631231c6"
|
||||
integrity sha512-cVYs5gyQH/qyut24hUvDznCfPrWiNMKNfPb9WmEoiU6ihlkscIbCfkmuKTtspVLWRdl0LqjYEC7vfnPv17HWhw==
|
||||
|
||||
"@prisma/engines@3.9.0-58.bcc2ff906db47790ee902e7bbc76d7ffb1893009":
|
||||
version "3.9.0-58.bcc2ff906db47790ee902e7bbc76d7ffb1893009"
|
||||
resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-3.9.0-58.bcc2ff906db47790ee902e7bbc76d7ffb1893009.tgz#e5c345cdedb7be83d11c1e0c5ab61d866b411256"
|
||||
integrity sha512-qM+uJbkelB21bnK44gYE049YTHIjHysOuj0mj5U2gDGyNLfmiazlggzFPCgEjgme4U5YB2tYs6Z5Hq08Kl8pjA==
|
||||
"@prisma/engines@3.10.0-50.73e60b76d394f8d37d8ebd1f8918c79029f0db86":
|
||||
version "3.10.0-50.73e60b76d394f8d37d8ebd1f8918c79029f0db86"
|
||||
resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-3.10.0-50.73e60b76d394f8d37d8ebd1f8918c79029f0db86.tgz#2964113729a78b8b21e186b5592affd1fde73c16"
|
||||
integrity sha512-LjRssaWu9w2SrXitofnutRIyURI7l0veQYIALz7uY4shygM9nMcK3omXcObRm7TAcw3Z+9ytfK1B+ySOsOesxQ==
|
||||
|
||||
"@samverschueren/stream-to-observable@^0.3.0":
|
||||
version "0.3.1"
|
||||
@ -4505,66 +4522,131 @@
|
||||
resolved "https://registry.yarnpkg.com/@swc/core-android-arm-eabi/-/core-android-arm-eabi-1.2.138.tgz#4605fa4afc0bb515798a7b7ebd274eb06f67775b"
|
||||
integrity sha512-N79aTHj/jZNa8nXjOrfAaYYBkJxCQ9ZVFikQKSbBETU8usk7qAWDdCs94Y0q/Sow+9uiqguRVOrPFKSrN8LMTg==
|
||||
|
||||
"@swc/core-android-arm-eabi@1.2.151":
|
||||
version "1.2.151"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core-android-arm-eabi/-/core-android-arm-eabi-1.2.151.tgz#e44fe75b2d8ba4685fbbf5727082b58b13bb2775"
|
||||
integrity sha512-Suk3IcHdha33K4hq9tfBCwkXJsENh7kjXCseLqL8Yvy8QobqkXjf1fcoJxX9BdCmPwsKmIw0ZgCBYR+Hl83M2w==
|
||||
|
||||
"@swc/core-android-arm64@1.2.138":
|
||||
version "1.2.138"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core-android-arm64/-/core-android-arm64-1.2.138.tgz#7bb94a78d7253ca8b6ec92be435c5a7686dbd68c"
|
||||
integrity sha512-ZNRqTjZpNrB39pCX5OmtnNTnzU3X1GjZX2xDouS1jknEE+TPz1ZJsM4zNlz6AObd7caJhU7qRyWNDM0nlcnJZQ==
|
||||
|
||||
"@swc/core-android-arm64@1.2.151":
|
||||
version "1.2.151"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core-android-arm64/-/core-android-arm64-1.2.151.tgz#8b7d02c8aed574a1cd5c312780abae9e17db159e"
|
||||
integrity sha512-HZVy69dVWT5RgrMJMRK5aiicPmhzkyCHAexApYAHYLgAIhsxL7uoAIPmuRKRkrKNJjrwsWL7H27bBH5bddRDvg==
|
||||
|
||||
"@swc/core-darwin-arm64@1.2.138":
|
||||
version "1.2.138"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.2.138.tgz#8a31dbdb90626f503a837ee71fa3bb7866ac3eb1"
|
||||
integrity sha512-DlT0s3Iw3bmOCk4jln0Q9AC1H7q75bZojyODcPXQ2T24s6LcBeD1lNAfyQ2RmaQJTlBM04LjNYqvjA2HAR4ckw==
|
||||
|
||||
"@swc/core-darwin-arm64@1.2.151":
|
||||
version "1.2.151"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.2.151.tgz#dc241a17bc920b7ece073579e3f9059ce0dc5ae5"
|
||||
integrity sha512-Ql7rXMu+IC76TemRtkt+opl5iSpX2ApAXVSfvf6afNVTrfTKLpDwiR3ySRRlG0FnNIv6TfOCJpHf655xp01S/g==
|
||||
|
||||
"@swc/core-darwin-x64@1.2.138":
|
||||
version "1.2.138"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.2.138.tgz#cc389708336dabc411a6d4705c2be17f9407054b"
|
||||
integrity sha512-+8ahwSnUTPCmpB1VkMTJdfcFU+ZGQ5JnA1dpSvDhB/u8wV2Dpk0ozpX+3xjqYXoUdhZvdHW1FxKZrhMhscJriA==
|
||||
|
||||
"@swc/core-darwin-x64@1.2.151":
|
||||
version "1.2.151"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.2.151.tgz#083dbf276d07c4537257bc25ad376602a34584b6"
|
||||
integrity sha512-N1OBIB7xatR5eybLo91ZhvMJMxT0zxRQURV/a9I8o5CyP4iLd1k8gmrYvBbtj08ohS8F9z7k/dFjxk/9ve5Drw==
|
||||
|
||||
"@swc/core-freebsd-x64@1.2.138":
|
||||
version "1.2.138"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core-freebsd-x64/-/core-freebsd-x64-1.2.138.tgz#2f29b1e8f133825fefb558a071f3bdb67dcf3c32"
|
||||
integrity sha512-4icXrpDBN2r24PIRF2DBZ9IPgnXnEqO7/bySIUoL7ul8su2yoRP4Xp3Xi+XP+uBvtrVttwYtzGPNikVggVSK1Q==
|
||||
|
||||
"@swc/core-freebsd-x64@1.2.151":
|
||||
version "1.2.151"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core-freebsd-x64/-/core-freebsd-x64-1.2.151.tgz#568a35267f1cccdef2fdc3e53c4f9a6095173706"
|
||||
integrity sha512-WVIRiDzuz+/W7BMjVtg1Cmk1+zmDT18Qq+Ygr9J6aFQ1JQUkLEE1pvtkGD3JIEa6Jhz/VwM6AFHtY5o1CrZ21w==
|
||||
|
||||
"@swc/core-linux-arm-gnueabihf@1.2.138":
|
||||
version "1.2.138"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.2.138.tgz#255c2011d865ff8f8118753f8900b51545c30000"
|
||||
integrity sha512-YdEKUvT9GGBEsKSyXc/YJ0cWSetBV3JhxouYLCv4AoQsTrDU5vDQDFUWlT21pzlbwC66ffbpYxnugpsqBm5XKg==
|
||||
|
||||
"@swc/core-linux-arm64-gnu@1.2.138", "@swc/core-linux-arm64-gnu@^1.2.136":
|
||||
"@swc/core-linux-arm-gnueabihf@1.2.151":
|
||||
version "1.2.151"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.2.151.tgz#24859f442a255220ca1caa7f8f5f087f2c22fd08"
|
||||
integrity sha512-pfBrIUwu3cR/M7DzDCUJAw9jFKXvJ/Ge8auFk07lRb+JcDnPm0XxLyrLqGvNQWdcHgXeXfmnS4fMQxdb9GUN1w==
|
||||
|
||||
"@swc/core-linux-arm64-gnu@1.2.138":
|
||||
version "1.2.138"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.2.138.tgz#89813e14240bde17aaa914a47e84626a10ae13ec"
|
||||
integrity sha512-cn/YrVvghCgSpagzHins1BQnJ07J53aCvlp57iXDA2xfH/HwXTijIy+UzqpQaLeKKQ8gMXmfzj/M7WklccN8jw==
|
||||
|
||||
"@swc/core-linux-arm64-musl@1.2.138", "@swc/core-linux-arm64-musl@^1.2.136":
|
||||
"@swc/core-linux-arm64-gnu@1.2.151":
|
||||
version "1.2.151"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.2.151.tgz#a4ae2d8f7b0cfb0836466ca57b608be7505b13e7"
|
||||
integrity sha512-M+BTkTdPY7gteM+0dYz9wrU/j9taL4ccqPEHkDEKP21lS24y99UtuKsvdBLzDm/6ShBVLFAkgIBPu5cEb7y6ig==
|
||||
|
||||
"@swc/core-linux-arm64-musl@1.2.138":
|
||||
version "1.2.138"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.2.138.tgz#c33351846218a4bd471505c9215233608f648ab9"
|
||||
integrity sha512-aYoeZ46gaewTYYShHwlYhL8ARrLILiEnTWJFEWoUfAfbDwi4zaLyymRYmdpUyRHr+D9jloM5BKFNWnRPBTyCEg==
|
||||
|
||||
"@swc/core-linux-x64-gnu@1.2.138", "@swc/core-linux-x64-gnu@^1.2.136":
|
||||
"@swc/core-linux-arm64-musl@1.2.151":
|
||||
version "1.2.151"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.2.151.tgz#16785fa421244d9df02123ce8b4bf8964b37412a"
|
||||
integrity sha512-7A+yTtSvPJVwO8X1cxUbD/PVCx8G9MKn83G9pH/r+9sQMBXqxyw6/NR0DG6nMMiyOmJkmYWgh5mO47BN7WC4dQ==
|
||||
|
||||
"@swc/core-linux-x64-gnu@1.2.138":
|
||||
version "1.2.138"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.2.138.tgz#0be2226c7c701d8f58051ca47e78f24d479a9faa"
|
||||
integrity sha512-gt9qP426kkIx4Yu2Dd9U2S44OE8ynRi47rt2HvdHaBlMsGfMH28EyMet3UT61ZVHMEoDxADQctz0JD1/29Ha1Q==
|
||||
|
||||
"@swc/core-linux-x64-musl@1.2.138", "@swc/core-linux-x64-musl@^1.2.136":
|
||||
"@swc/core-linux-x64-gnu@1.2.151":
|
||||
version "1.2.151"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.2.151.tgz#b0717cb662becec95d306632fbd40f612d3db700"
|
||||
integrity sha512-ORlbN3wf1w0IQGjGToYYC/hV/Vwfcs88Ohfxc4X+IQaw/VxKG6/XT65c0btK640F2TVhvhH1MbYFJJlsycsW7g==
|
||||
|
||||
"@swc/core-linux-x64-musl@1.2.138":
|
||||
version "1.2.138"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.2.138.tgz#07feede753206a4858dd275a0a4f99501909010e"
|
||||
integrity sha512-lySbIVGApaDQVKPwH8D+9J5dkrawJTrBm86vY7F9sDPR5yCq5Buxx6Pn1X6VKE6e5vlEEb1zbVQmCrFgdUcgig==
|
||||
|
||||
"@swc/core-linux-x64-musl@1.2.151":
|
||||
version "1.2.151"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.2.151.tgz#7d703b3a96da37538bd69e4582c8ee70c9d36a37"
|
||||
integrity sha512-r6odKE3+9+ReVdnNTZnICt5tscyFFtP4GFcmPQzBSlVoD9LZX6O4WeOlFXn77rVK/+205n2ag/KkKgZH+vdPuQ==
|
||||
|
||||
"@swc/core-win32-arm64-msvc@1.2.138":
|
||||
version "1.2.138"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.2.138.tgz#04e7dbfefb2e933433be32254c52c65add15c086"
|
||||
integrity sha512-UmDtaC9ds1SNNfhYrHW1JvBhy7wKb/Y9RcQOsfG3StxqqnYkOWDkQt9dY5O9lAG8Iw/TCxzjJhm6ul48eMv9OQ==
|
||||
|
||||
"@swc/core-win32-arm64-msvc@1.2.151":
|
||||
version "1.2.151"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.2.151.tgz#aa97ef1df5e740c0ae1b4b0586f6c544983f11a7"
|
||||
integrity sha512-jnjJTNHpLhBaPwRgiKv1TdrMljL88ePqMCdVMantyd7yl4lP0D2e5/xR9ysR9S4EGcUnOyo9w8WUYhx/TioMZw==
|
||||
|
||||
"@swc/core-win32-ia32-msvc@1.2.138":
|
||||
version "1.2.138"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.2.138.tgz#7d897c97ac5338e8a947d6c0c032e8068b521a2e"
|
||||
integrity sha512-evapKq/jVKMI5KDXUvpu3rhYf/L0VIg92TTphpxJSNjo7k5w9n68RY3MXtm1BmtCR4ZWtx0OEXzr9ckUDcqZDA==
|
||||
|
||||
"@swc/core-win32-ia32-msvc@1.2.151":
|
||||
version "1.2.151"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.2.151.tgz#6ab6889078ef820a7c644d7df72403cbb534d4e2"
|
||||
integrity sha512-hSCxAiyDDXKvdUExj4jSIhzWFePqoqak1qdNUjlhEhEinDG8T8PTRCLalyW6fqZDcLf6Tqde7H79AqbfhRlYGQ==
|
||||
|
||||
"@swc/core-win32-x64-msvc@1.2.138":
|
||||
version "1.2.138"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.2.138.tgz#6a54a72ed035d3b327f2576f4a586da093dc4898"
|
||||
integrity sha512-wYrARtnPg/svsQd0oovbth2JAhOugAgbnaOS0CMiWB4vaFBx+1GHJl5wzdhh9jt1kzsu4xZ4237tUeMH+s6d0A==
|
||||
|
||||
"@swc/core-win32-x64-msvc@1.2.151":
|
||||
version "1.2.151"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.2.151.tgz#525c9554da57c0d4b07956349680b1dc9c4dee4f"
|
||||
integrity sha512-HOkqcJWCChps83Maj0M5kifPDuZ2sGPqpLM67poawspTFkBh0QJ9TMmxW1doQw+74cqsTpRi1ewr/KhsN18i5g==
|
||||
|
||||
"@swc/core@^1.2.119":
|
||||
version "1.2.138"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.2.138.tgz#e54d8488094f7f90cb00455cb0380693c0935865"
|
||||
@ -4584,6 +4666,25 @@
|
||||
"@swc/core-win32-ia32-msvc" "1.2.138"
|
||||
"@swc/core-win32-x64-msvc" "1.2.138"
|
||||
|
||||
"@swc/core@^1.2.146":
|
||||
version "1.2.151"
|
||||
resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.2.151.tgz#8d4154a2e4ced74c5fd215c5905baa08775553d6"
|
||||
integrity sha512-oHgqKwK/Djv765zUHPiGqfMCaKIxXTgQyyCUBKLBQfAJwe/7FVobQ2fghBp4FsZA/NE1LZBmMPpRZNQwlGjeHw==
|
||||
optionalDependencies:
|
||||
"@swc/core-android-arm-eabi" "1.2.151"
|
||||
"@swc/core-android-arm64" "1.2.151"
|
||||
"@swc/core-darwin-arm64" "1.2.151"
|
||||
"@swc/core-darwin-x64" "1.2.151"
|
||||
"@swc/core-freebsd-x64" "1.2.151"
|
||||
"@swc/core-linux-arm-gnueabihf" "1.2.151"
|
||||
"@swc/core-linux-arm64-gnu" "1.2.151"
|
||||
"@swc/core-linux-arm64-musl" "1.2.151"
|
||||
"@swc/core-linux-x64-gnu" "1.2.151"
|
||||
"@swc/core-linux-x64-musl" "1.2.151"
|
||||
"@swc/core-win32-arm64-msvc" "1.2.151"
|
||||
"@swc/core-win32-ia32-msvc" "1.2.151"
|
||||
"@swc/core-win32-x64-msvc" "1.2.151"
|
||||
|
||||
"@tootallnate/once@1":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
|
||||
@ -6773,7 +6874,7 @@ browserslist@^4.19.1:
|
||||
node-releases "^2.0.1"
|
||||
picocolors "^1.0.0"
|
||||
|
||||
bs-logger@0.x:
|
||||
bs-logger@0.x, bs-logger@^0.2.6:
|
||||
version "0.2.6"
|
||||
resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8"
|
||||
integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==
|
||||
@ -8844,208 +8945,219 @@ es6-shim@^0.35.5:
|
||||
resolved "https://registry.yarnpkg.com/es6-shim/-/es6-shim-0.35.6.tgz#d10578301a83af2de58b9eadb7c2c9945f7388a0"
|
||||
integrity sha512-EmTr31wppcaIAgblChZiuN/l9Y7DPyw8Xtbg7fIVngn6zMW+IEBJDJngeKC3x6wr0V/vcA2wqeFnaw1bFJbDdA==
|
||||
|
||||
esbuild-android-arm64@0.13.13:
|
||||
version "0.13.13"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.13.13.tgz#da07b5fb2daf7d83dcd725f7cf58a6758e6e702a"
|
||||
integrity sha512-T02aneWWguJrF082jZworjU6vm8f4UQ+IH2K3HREtlqoY9voiJUwHLRL6khRlsNLzVglqgqb7a3HfGx7hAADCQ==
|
||||
esbuild-android-arm64@0.14.11:
|
||||
version "0.14.11"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.11.tgz#b8b34e35a5b43880664ac7a3fbc70243d7ed894f"
|
||||
integrity sha512-6iHjgvMnC/SzDH8TefL+/3lgCjYWwAd1LixYfmz/TBPbDQlxcuSkX0yiQgcJB9k+ibZ54yjVXziIwGdlc+6WNw==
|
||||
|
||||
esbuild-android-arm64@0.14.14:
|
||||
version "0.14.14"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.14.tgz#3705f32f209deeb11c275af47c298c8783dd5f0c"
|
||||
integrity sha512-be/Uw6DdpQiPfula1J4bdmA+wtZ6T3BRCZsDMFB5X+k0Gp8TIh9UvmAcqvKNnbRAafSaXG3jPCeXxDKqnc8hFQ==
|
||||
|
||||
esbuild-darwin-64@0.13.13:
|
||||
version "0.13.13"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.13.13.tgz#e94e9fd3b4b5455a2e675cd084a19a71b6904bbf"
|
||||
integrity sha512-wkaiGAsN/09X9kDlkxFfbbIgR78SNjMOfUhoel3CqKBDsi9uZhw7HBNHNxTzYUK8X8LAKFpbODgcRB3b/I8gHA==
|
||||
esbuild-darwin-64@0.14.11:
|
||||
version "0.14.11"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.11.tgz#ba805de98c0412e50fcd0636451797da157b0625"
|
||||
integrity sha512-olq84ikh6TiBcrs3FnM4eR5VPPlcJcdW8BnUz/lNoEWYifYQ+Po5DuYV1oz1CTFMw4k6bQIZl8T3yxL+ZT2uvQ==
|
||||
|
||||
esbuild-darwin-64@0.14.14:
|
||||
version "0.14.14"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.14.tgz#c07e4eae6d938300a2d330ea82494c55bcea84e5"
|
||||
integrity sha512-BEexYmjWafcISK8cT6O98E3TfcLuZL8DKuubry6G54n2+bD4GkoRD6HYUOnCkfl2p7jodA+s4369IjSFSWjtHg==
|
||||
|
||||
esbuild-darwin-arm64@0.13.13:
|
||||
version "0.13.13"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.13.13.tgz#8c320eafbb3ba2c70d8062128c5b71503e342471"
|
||||
integrity sha512-b02/nNKGSV85Gw9pUCI5B48AYjk0vFggDeom0S6QMP/cEDtjSh1WVfoIFNAaLA0MHWfue8KBwoGVsN7rBshs4g==
|
||||
esbuild-darwin-arm64@0.14.11:
|
||||
version "0.14.11"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.11.tgz#4d3573e448af76ce33e16231f3d9f878542d6fe8"
|
||||
integrity sha512-Jj0ieWLREPBYr/TZJrb2GFH8PVzDqiQWavo1pOFFShrcmHWDBDrlDxPzEZ67NF/Un3t6sNNmeI1TUS/fe1xARg==
|
||||
|
||||
esbuild-darwin-arm64@0.14.14:
|
||||
version "0.14.14"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.14.tgz#a8631e13a51a6f784fb0906e2a64c6ab53988755"
|
||||
integrity sha512-tnBKm41pDOB1GtZ8q/w26gZlLLRzVmP8fdsduYjvM+yFD7E2DLG4KbPAqFMWm4Md9B+DitBglP57FY7AznxbTg==
|
||||
|
||||
esbuild-freebsd-64@0.13.13:
|
||||
version "0.13.13"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.13.13.tgz#ce0ca5b8c4c274cfebc9326f9b316834bd9dd151"
|
||||
integrity sha512-ALgXYNYDzk9YPVk80A+G4vz2D22Gv4j4y25exDBGgqTcwrVQP8rf/rjwUjHoh9apP76oLbUZTmUmvCMuTI1V9A==
|
||||
esbuild-freebsd-64@0.14.11:
|
||||
version "0.14.11"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.11.tgz#9294e6ab359ec93590ab097b0f2017de7c78ab4d"
|
||||
integrity sha512-C5sT3/XIztxxz/zwDjPRHyzj/NJFOnakAanXuyfLDwhwupKPd76/PPHHyJx6Po6NI6PomgVp/zi6GRB8PfrOTA==
|
||||
|
||||
esbuild-freebsd-64@0.14.14:
|
||||
version "0.14.14"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.14.tgz#c280c2b944746b27ee6c6487c2691865c90bed2e"
|
||||
integrity sha512-Q9Rx6sgArOHalQtNwAaIzJ6dnQ8A+I7f/RsQsdkS3JrdzmnlFo8JEVofTmwVQLoIop7OKUqIVOGP4PoQcwfVMA==
|
||||
|
||||
esbuild-freebsd-arm64@0.13.13:
|
||||
version "0.13.13"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.13.13.tgz#463da17562fdcfdf03b3b94b28497d8d8dcc8f62"
|
||||
integrity sha512-uFvkCpsZ1yqWQuonw5T1WZ4j59xP/PCvtu6I4pbLejhNo4nwjW6YalqnBvBSORq5/Ifo9S/wsIlVHzkzEwdtlw==
|
||||
esbuild-freebsd-arm64@0.14.11:
|
||||
version "0.14.11"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.11.tgz#ae3e0b09173350b66cf8321583c9a1c1fcb8bb55"
|
||||
integrity sha512-y3Llu4wbs0bk4cwjsdAtVOesXb6JkdfZDLKMt+v1U3tOEPBdSu6w8796VTksJgPfqvpX22JmPLClls0h5p+L9w==
|
||||
|
||||
esbuild-freebsd-arm64@0.14.14:
|
||||
version "0.14.14"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.14.tgz#aa4e21276efcf20e5ab2487e91ca1d789573189b"
|
||||
integrity sha512-TJvq0OpLM7BkTczlyPIphcvnwrQwQDG1HqxzoYePWn26SMUAlt6wrLnEvxdbXAvNvDLVzG83kA+JimjK7aRNBA==
|
||||
|
||||
esbuild-linux-32@0.13.13:
|
||||
version "0.13.13"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.13.13.tgz#2035793160da2c4be48a929e5bafb14a31789acc"
|
||||
integrity sha512-yxR9BBwEPs9acVEwTrEE2JJNHYVuPQC9YGjRfbNqtyfK/vVBQYuw8JaeRFAvFs3pVJdQD0C2BNP4q9d62SCP4w==
|
||||
esbuild-linux-32@0.14.11:
|
||||
version "0.14.11"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.11.tgz#ddadbc7038aa5a6b1675bb1503cf79a0cbf1229a"
|
||||
integrity sha512-Cg3nVsxArjyLke9EuwictFF3Sva+UlDTwHIuIyx8qpxRYAOUTmxr2LzYrhHyTcGOleLGXUXYsnUVwKqnKAgkcg==
|
||||
|
||||
esbuild-linux-32@0.14.14:
|
||||
version "0.14.14"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.14.tgz#3db4d929239203ce38a9060d5419ac6a6d28846c"
|
||||
integrity sha512-h/CrK9Baimt5VRbu8gqibWV7e1P9l+mkanQgyOgv0Ng3jHT1NVFC9e6rb1zbDdaJVmuhWX5xVliUA5bDDCcJeg==
|
||||
|
||||
esbuild-linux-64@0.13.13:
|
||||
version "0.13.13"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.13.13.tgz#fbe4802a8168c6d339d0749f977b099449b56f22"
|
||||
integrity sha512-kzhjlrlJ+6ESRB/n12WTGll94+y+HFeyoWsOrLo/Si0s0f+Vip4b8vlnG0GSiS6JTsWYAtGHReGczFOaETlKIw==
|
||||
esbuild-linux-64@0.14.11:
|
||||
version "0.14.11"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.11.tgz#d698e3ce3a231ddfeec6b5df8c546ae8883fcd88"
|
||||
integrity sha512-oeR6dIrrojr8DKVrxtH3xl4eencmjsgI6kPkDCRIIFwv4p+K7ySviM85K66BN01oLjzthpUMvBVfWSJkBLeRbg==
|
||||
|
||||
esbuild-linux-64@0.14.14:
|
||||
version "0.14.14"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.14.tgz#f880026254c1f565a7a10fdebb7cff9b083a127d"
|
||||
integrity sha512-IC+wAiIg/egp5OhQp4W44D9PcBOH1b621iRn1OXmlLzij9a/6BGr9NMIL4CRwz4j2kp3WNZu5sT473tYdynOuQ==
|
||||
|
||||
esbuild-linux-arm64@0.13.13:
|
||||
version "0.13.13"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.13.13.tgz#f08d98df28d436ed4aad1529615822bb74d4d978"
|
||||
integrity sha512-KMrEfnVbmmJxT3vfTnPv/AiXpBFbbyExH13BsUGy1HZRPFMi5Gev5gk8kJIZCQSRfNR17aqq8sO5Crm2KpZkng==
|
||||
esbuild-linux-arm64@0.14.11:
|
||||
version "0.14.11"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.11.tgz#85faea9fa99ad355b5e3b283197a4dfd0a110fe7"
|
||||
integrity sha512-+e6ZCgTFQYZlmg2OqLkg1jHLYtkNDksxWDBWNtI4XG4WxuOCUErLqfEt9qWjvzK3XBcCzHImrajkUjO+rRkbMg==
|
||||
|
||||
esbuild-linux-arm64@0.14.14:
|
||||
version "0.14.14"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.14.tgz#a34bc3076e50b109c3b8c8bad9c146e35942322b"
|
||||
integrity sha512-6QVul3RI4M5/VxVIRF/I5F+7BaxzR3DfNGoqEVSCZqUbgzHExPn+LXr5ly1C7af2Kw4AHpo+wDqx8A4ziP9avw==
|
||||
|
||||
esbuild-linux-arm@0.13.13:
|
||||
version "0.13.13"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.13.13.tgz#6f968c3a98b64e30c80b212384192d0cfcb32e7f"
|
||||
integrity sha512-hXub4pcEds+U1TfvLp1maJ+GHRw7oizvzbGRdUvVDwtITtjq8qpHV5Q5hWNNn6Q+b3b2UxF03JcgnpzCw96nUQ==
|
||||
esbuild-linux-arm@0.14.11:
|
||||
version "0.14.11"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.11.tgz#74cbcf0b8a22c8401bcbcd6ebd4cbf2baca8b7b4"
|
||||
integrity sha512-vcwskfD9g0tojux/ZaTJptJQU3a7YgTYsptK1y6LQ/rJmw7U5QJvboNawqM98Ca3ToYEucfCRGbl66OTNtp6KQ==
|
||||
|
||||
esbuild-linux-arm@0.14.14:
|
||||
version "0.14.14"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.14.tgz#231ffd12fef69ee06365d4c94b69850e4830e927"
|
||||
integrity sha512-gxpOaHOPwp7zSmcKYsHrtxabScMqaTzfSQioAMUaB047YiMuDBzqVcKBG8OuESrYkGrL9DDljXr/mQNg7pbdaQ==
|
||||
|
||||
esbuild-linux-mips64le@0.13.13:
|
||||
version "0.13.13"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.13.13.tgz#690c78dc4725efe7d06a1431287966fbf7774c7f"
|
||||
integrity sha512-cJT9O1LYljqnnqlHaS0hdG73t7hHzF3zcN0BPsjvBq+5Ad47VJun+/IG4inPhk8ta0aEDK6LdP+F9299xa483w==
|
||||
esbuild-linux-mips64le@0.14.11:
|
||||
version "0.14.11"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.11.tgz#490429211a3233f5cbbd8575b7758b897e42979a"
|
||||
integrity sha512-Rrs99L+p54vepmXIb87xTG6ukrQv+CzrM8eoeR+r/OFL2Rg8RlyEtCeshXJ2+Q66MXZOgPJaokXJZb9snq28bw==
|
||||
|
||||
esbuild-linux-mips64le@0.14.14:
|
||||
version "0.14.14"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.14.tgz#bd00570e3a30422224b732c7a5f262146c357403"
|
||||
integrity sha512-4Jl5/+xoINKbA4cesH3f4R+q0vltAztZ6Jm8YycS8lNhN1pgZJBDxWfI6HUMIAdkKlIpR1PIkA9aXQgZ8sxFAg==
|
||||
|
||||
esbuild-linux-ppc64le@0.13.13:
|
||||
version "0.13.13"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.13.13.tgz#7ec9048502de46754567e734aae7aebd2df6df02"
|
||||
integrity sha512-+rghW8st6/7O6QJqAjVK3eXzKkZqYAw6LgHv7yTMiJ6ASnNvghSeOcIvXFep3W2oaJc35SgSPf21Ugh0o777qQ==
|
||||
esbuild-linux-ppc64le@0.14.11:
|
||||
version "0.14.11"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.11.tgz#fc79d60710213b5b98345f5b138d48245616827a"
|
||||
integrity sha512-JyzziGAI0D30Vyzt0HDihp4s1IUtJ3ssV2zx9O/c+U/dhUHVP2TmlYjzCfCr2Q6mwXTeloDcLS4qkyvJtYptdQ==
|
||||
|
||||
esbuild-linux-ppc64le@0.14.14:
|
||||
version "0.14.14"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.14.tgz#430609413fd9e04d9def4e3f06726b031b23d825"
|
||||
integrity sha512-BitW37GxeebKxqYNl4SVuSdnIJAzH830Lr6Mkq3pBHXtzQay0vK+IeOR/Ele1GtNVJ+/f8wYM53tcThkv5SC5w==
|
||||
|
||||
esbuild-linux-s390x@0.14.11:
|
||||
version "0.14.11"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.11.tgz#ca4b93556bbba6cc95b0644f2ee93c982165ba07"
|
||||
integrity sha512-DoThrkzunZ1nfRGoDN6REwmo8ZZWHd2ztniPVIR5RMw/Il9wiWEYBahb8jnMzQaSOxBsGp0PbyJeVLTUatnlcw==
|
||||
|
||||
esbuild-linux-s390x@0.14.14:
|
||||
version "0.14.14"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.14.tgz#2f0d8cbfe53cf3cb97f6372549a41a8051dbd689"
|
||||
integrity sha512-vLj6p76HOZG3wfuTr5MyO3qW5iu8YdhUNxuY+tx846rPo7GcKtYSPMusQjeVEfZlJpSYoR+yrNBBxq+qVF9zrw==
|
||||
|
||||
esbuild-netbsd-64@0.13.13:
|
||||
version "0.13.13"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.13.13.tgz#439bdaefffa03a8fa84324f5d83d636f548a2de3"
|
||||
integrity sha512-A/B7rwmzPdzF8c3mht5TukbnNwY5qMJqes09ou0RSzA5/jm7Jwl/8z853ofujTFOLhkNHUf002EAgokzSgEMpQ==
|
||||
esbuild-netbsd-64@0.14.11:
|
||||
version "0.14.11"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.11.tgz#edb340bc6653c88804cac2253e21b74258fce165"
|
||||
integrity sha512-12luoRQz+6eihKYh1zjrw0CBa2aw3twIiHV/FAfjh2NEBDgJQOY4WCEUEN+Rgon7xmLh4XUxCQjnwrvf8zhACw==
|
||||
|
||||
esbuild-netbsd-64@0.14.14:
|
||||
version "0.14.14"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.14.tgz#3e44de35e1add7e9582f3c0d2558d86aafbc813b"
|
||||
integrity sha512-fn8looXPQhpVqUyCBWUuPjesH+yGIyfbIQrLKG05rr1Kgm3rZD/gaYrd3Wpmf5syVZx70pKZPvdHp8OTA+y7cQ==
|
||||
|
||||
esbuild-openbsd-64@0.13.13:
|
||||
version "0.13.13"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.13.13.tgz#c9958e5291a00a3090c1ec482d6bcdf2d5b5d107"
|
||||
integrity sha512-szwtuRA4rXKT3BbwoGpsff6G7nGxdKgUbW9LQo6nm0TVCCjDNDC/LXxT994duIW8Tyq04xZzzZSW7x7ttDiw1w==
|
||||
esbuild-openbsd-64@0.14.11:
|
||||
version "0.14.11"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.11.tgz#caeff5f946f79a60ce7bcf88871ca4c71d3476e8"
|
||||
integrity sha512-l18TZDjmvwW6cDeR4fmizNoxndyDHamGOOAenwI4SOJbzlJmwfr0jUgjbaXCUuYVOA964siw+Ix+A+bhALWg8Q==
|
||||
|
||||
esbuild-openbsd-64@0.14.14:
|
||||
version "0.14.14"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.14.tgz#04710ef1d01cd9f15d54f50d20b5a3778f8306a2"
|
||||
integrity sha512-HdAnJ399pPff3SKbd8g+P4o5znseni5u5n5rJ6Z7ouqOdgbOwHe2ofZbMow17WMdNtz1IyOZk2Wo9Ve6/lZ4Rg==
|
||||
|
||||
esbuild-sunos-64@0.13.13:
|
||||
version "0.13.13"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.13.13.tgz#ac9ead8287379cd2f6d00bd38c5997fda9c1179e"
|
||||
integrity sha512-ihyds9O48tVOYF48iaHYUK/boU5zRaLOXFS+OOL3ceD39AyHo46HVmsJLc7A2ez0AxNZCxuhu+P9OxfPfycTYQ==
|
||||
esbuild-sunos-64@0.14.11:
|
||||
version "0.14.11"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.11.tgz#90ce7e1749c2958a53509b4bae7b8f7d98f276d6"
|
||||
integrity sha512-bmYzDtwASBB8c+0/HVOAiE9diR7+8zLm/i3kEojUH2z0aIs6x/S4KiTuT5/0VKJ4zk69kXel1cNWlHBMkmavQg==
|
||||
|
||||
esbuild-sunos-64@0.14.14:
|
||||
version "0.14.14"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.14.tgz#8e583dd92c5c7ac4303ddc37f588e44211e04e19"
|
||||
integrity sha512-bmDHa99ulsGnYlh/xjBEfxoGuC8CEG5OWvlgD+pF7bKKiVTbtxqVCvOGEZeoDXB+ja6AvHIbPxrEE32J+m5nqQ==
|
||||
|
||||
esbuild-wasm@0.14.11:
|
||||
version "0.14.11"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-wasm/-/esbuild-wasm-0.14.11.tgz#bd09f4c42969cddcae39007d284f8ef747aae85d"
|
||||
integrity sha512-9e1R6hv0hiU+BkJI2edqUuWfXUbOP2Mox+Ijl/uY1vLLlSsunkrcADqD/4Rz+VCEDzw6ecscJM+uJqR2fRmEUg==
|
||||
|
||||
esbuild-wasm@0.14.14:
|
||||
version "0.14.14"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-wasm/-/esbuild-wasm-0.14.14.tgz#d4c8d5fc405939a2234a31abf00967dfd1da1caa"
|
||||
integrity sha512-qTjK4MWnYtQHCMGg2qDUqeFYXfVvYq5qJkQTIsOV4VZCknoYePVaDTG9ygEB9Ct0kc0DWs7IrS6Ja+GjY62Kzw==
|
||||
|
||||
esbuild-windows-32@0.13.13:
|
||||
version "0.13.13"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.13.13.tgz#a3820fc86631ca594cb7b348514b5cc3f058cfd6"
|
||||
integrity sha512-h2RTYwpG4ldGVJlbmORObmilzL8EECy8BFiF8trWE1ZPHLpECE9//J3Bi+W3eDUuv/TqUbiNpGrq4t/odbayUw==
|
||||
esbuild-windows-32@0.14.11:
|
||||
version "0.14.11"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.11.tgz#d067f4ce15b29efba6336e6a23597120fafe49ec"
|
||||
integrity sha512-J1Ys5hMid8QgdY00OBvIolXgCQn1ARhYtxPnG6ESWNTty3ashtc4+As5nTrsErnv8ZGUcWZe4WzTP/DmEVX1UQ==
|
||||
|
||||
esbuild-windows-32@0.14.14:
|
||||
version "0.14.14"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.14.tgz#6d293ddfb71229f21cc13d85d5d2f43e8131693b"
|
||||
integrity sha512-6tVooQcxJCNenPp5GHZBs/RLu31q4B+BuF4MEoRxswT+Eq2JGF0ZWDRQwNKB8QVIo3t6Svc5wNGez+CwKNQjBg==
|
||||
|
||||
esbuild-windows-64@0.13.13:
|
||||
version "0.13.13"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.13.13.tgz#1da748441f228d75dff474ddb7d584b81887323c"
|
||||
integrity sha512-oMrgjP4CjONvDHe7IZXHrMk3wX5Lof/IwFEIbwbhgbXGBaN2dke9PkViTiXC3zGJSGpMvATXVplEhlInJ0drHA==
|
||||
esbuild-windows-64@0.14.11:
|
||||
version "0.14.11"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.11.tgz#13e86dd37a6cd61a5276fa2d271342d0f74da864"
|
||||
integrity sha512-h9FmMskMuGeN/9G9+LlHPAoiQk9jlKDUn9yA0MpiGzwLa82E7r1b1u+h2a+InprbSnSLxDq/7p5YGtYVO85Mlg==
|
||||
|
||||
esbuild-windows-64@0.14.14:
|
||||
version "0.14.14"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.14.tgz#08a36844b69542f8ec1cb33a5ddcea02b9d0b2e8"
|
||||
integrity sha512-kl3BdPXh0/RD/dad41dtzj2itMUR4C6nQbXQCyYHHo4zoUoeIXhpCrSl7BAW1nv5EFL8stT1V+TQVXGZca5A2A==
|
||||
|
||||
esbuild-windows-arm64@0.13.13:
|
||||
version "0.13.13"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.13.13.tgz#06dfa52a6b178a5932a9a6e2fdb240c09e6da30c"
|
||||
integrity sha512-6fsDfTuTvltYB5k+QPah/x7LrI2+OLAJLE3bWLDiZI6E8wXMQU+wLqtEO/U/RvJgVY1loPs5eMpUBpVajczh1A==
|
||||
esbuild-windows-arm64@0.14.11:
|
||||
version "0.14.11"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.11.tgz#e8edfdf1d712085e6dc3fba18a0c225aaae32b75"
|
||||
integrity sha512-dZp7Krv13KpwKklt9/1vBFBMqxEQIO6ri7Azf8C+ob4zOegpJmha2XY9VVWP/OyQ0OWk6cEeIzMJwInRZrzBUQ==
|
||||
|
||||
esbuild-windows-arm64@0.14.14:
|
||||
version "0.14.14"
|
||||
resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.14.tgz#ca747ce4066d5b8a79dbe48fe6ecd92d202e5366"
|
||||
integrity sha512-dCm1wTOm6HIisLanmybvRKvaXZZo4yEVrHh1dY0v582GThXJOzuXGja1HIQgV09RpSHYRL3m4KoUBL00l6SWEg==
|
||||
|
||||
esbuild@0.13.13:
|
||||
version "0.13.13"
|
||||
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.13.13.tgz#0b5399c20f219f663c8c1048436fb0f59ab17a41"
|
||||
integrity sha512-Z17A/R6D0b4s3MousytQ/5i7mTCbaF+Ua/yPfoe71vdTv4KBvVAvQ/6ytMngM2DwGJosl8WxaD75NOQl2QF26Q==
|
||||
esbuild@0.14.11:
|
||||
version "0.14.11"
|
||||
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.11.tgz#ac4acb78907874832afb704c3afe58ad37715c27"
|
||||
integrity sha512-xZvPtVj6yecnDeFb3KjjCM6i7B5TCAQZT77kkW/CpXTMnd6VLnRPKrUB1XHI1pSq6a4Zcy3BGueQ8VljqjDGCg==
|
||||
optionalDependencies:
|
||||
esbuild-android-arm64 "0.13.13"
|
||||
esbuild-darwin-64 "0.13.13"
|
||||
esbuild-darwin-arm64 "0.13.13"
|
||||
esbuild-freebsd-64 "0.13.13"
|
||||
esbuild-freebsd-arm64 "0.13.13"
|
||||
esbuild-linux-32 "0.13.13"
|
||||
esbuild-linux-64 "0.13.13"
|
||||
esbuild-linux-arm "0.13.13"
|
||||
esbuild-linux-arm64 "0.13.13"
|
||||
esbuild-linux-mips64le "0.13.13"
|
||||
esbuild-linux-ppc64le "0.13.13"
|
||||
esbuild-netbsd-64 "0.13.13"
|
||||
esbuild-openbsd-64 "0.13.13"
|
||||
esbuild-sunos-64 "0.13.13"
|
||||
esbuild-windows-32 "0.13.13"
|
||||
esbuild-windows-64 "0.13.13"
|
||||
esbuild-windows-arm64 "0.13.13"
|
||||
esbuild-android-arm64 "0.14.11"
|
||||
esbuild-darwin-64 "0.14.11"
|
||||
esbuild-darwin-arm64 "0.14.11"
|
||||
esbuild-freebsd-64 "0.14.11"
|
||||
esbuild-freebsd-arm64 "0.14.11"
|
||||
esbuild-linux-32 "0.14.11"
|
||||
esbuild-linux-64 "0.14.11"
|
||||
esbuild-linux-arm "0.14.11"
|
||||
esbuild-linux-arm64 "0.14.11"
|
||||
esbuild-linux-mips64le "0.14.11"
|
||||
esbuild-linux-ppc64le "0.14.11"
|
||||
esbuild-linux-s390x "0.14.11"
|
||||
esbuild-netbsd-64 "0.14.11"
|
||||
esbuild-openbsd-64 "0.14.11"
|
||||
esbuild-sunos-64 "0.14.11"
|
||||
esbuild-windows-32 "0.14.11"
|
||||
esbuild-windows-64 "0.14.11"
|
||||
esbuild-windows-arm64 "0.14.11"
|
||||
|
||||
esbuild@0.14.14:
|
||||
version "0.14.14"
|
||||
@ -12143,15 +12255,18 @@ jest-pnp-resolver@^1.2.2:
|
||||
resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c"
|
||||
integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==
|
||||
|
||||
jest-preset-angular@11.0.0:
|
||||
version "11.0.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-preset-angular/-/jest-preset-angular-11.0.0.tgz#4b1913e4baddf37a8b96d6215d9a647dcdd6f324"
|
||||
integrity sha512-+Vt6O2q/cvhbbrE4xplZjn3TqLcQpOtkk+zqoCFLW/Lo0fALEJIXECt1Ia288iJtxJU4qm7tLsQy1KmAaN+CzA==
|
||||
jest-preset-angular@11.1.1:
|
||||
version "11.1.1"
|
||||
resolved "https://registry.yarnpkg.com/jest-preset-angular/-/jest-preset-angular-11.1.1.tgz#cc1c0a1395727af332c439174fb689d92e853f6a"
|
||||
integrity sha512-ZlYiKJhAQSU9wIjncX59xutcj49R4MiDsTPSwZiwdTAHQvHm32MS6SGimQIVBqh1DukfwYX0NXKS0D/onLAsLQ==
|
||||
dependencies:
|
||||
esbuild "0.13.13"
|
||||
bs-logger "^0.2.6"
|
||||
esbuild-wasm "0.14.11"
|
||||
jest-environment-jsdom "^27.0.0"
|
||||
pretty-format "^27.0.0"
|
||||
ts-jest "^27.0.0"
|
||||
optionalDependencies:
|
||||
esbuild "0.14.11"
|
||||
|
||||
jest-regex-util@^26.0.0:
|
||||
version "26.0.0"
|
||||
@ -13556,6 +13671,11 @@ ms@2.1.2:
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
|
||||
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
|
||||
|
||||
ms@3.0.0-canary.1:
|
||||
version "3.0.0-canary.1"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-3.0.0-canary.1.tgz#c7b34fbce381492fd0b345d1cf56e14d67b77b80"
|
||||
integrity sha512-kh8ARjh8rMN7Du2igDRO9QJnqCb2xYTJxyQYK7vJJS4TvLLmsbyhiKpSW+t+y26gyOyMd0riphX0GeWKU3ky5g==
|
||||
|
||||
ms@^2.0.0, ms@^2.1.1:
|
||||
version "2.1.3"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
|
||||
@ -13688,13 +13808,13 @@ ngx-markdown@13.0.0:
|
||||
prismjs "^1.25.0"
|
||||
tslib "^2.3.0"
|
||||
|
||||
ngx-skeleton-loader@2.9.1:
|
||||
version "2.9.1"
|
||||
resolved "https://registry.yarnpkg.com/ngx-skeleton-loader/-/ngx-skeleton-loader-2.9.1.tgz#1e419ef66696a2017afc9c8cd0bc129d2c680ffb"
|
||||
integrity sha512-knFL2Ua/p60XUPH9ZrfgydiBHvvylny7jsVOXBtmACrYD7HcnuUl1uAEk/LvcN15tSjC9VnVwTIOU4i8LCSAgw==
|
||||
ngx-skeleton-loader@5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ngx-skeleton-loader/-/ngx-skeleton-loader-5.0.0.tgz#e0042de20b0159d3f97d03a696d68f39ceee383b"
|
||||
integrity sha512-6cz8UAu4WcYnBp/LnU053LCIwjKNZWX8GX1v3bvqQVdDa1ubsEeJm+CZxk5B8W2jP9CcFhvWrBlmmVUyl1Yxug==
|
||||
dependencies:
|
||||
perf-marks "^1.13.4"
|
||||
tslib "^1.10.0"
|
||||
tslib "^2.0.0"
|
||||
|
||||
ngx-stripe@13.0.0:
|
||||
version "13.0.0"
|
||||
@ -13988,12 +14108,12 @@ nwsapi@^2.2.0:
|
||||
resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7"
|
||||
integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==
|
||||
|
||||
nx@13.8.1:
|
||||
version "13.8.1"
|
||||
resolved "https://registry.yarnpkg.com/nx/-/nx-13.8.1.tgz#10e17dace55eb38f762ed212ded02e24797c8ac7"
|
||||
integrity sha512-8oHkh/Hli/OGGkumH8C9NArFjvFoNEgSfVkzCB7zoGddnIJlGAx+sSpHp0zJbXJV55Lk7iXbpKyeOJNnQDsEyQ==
|
||||
nx@13.8.5:
|
||||
version "13.8.5"
|
||||
resolved "https://registry.yarnpkg.com/nx/-/nx-13.8.5.tgz#4553170a7fd1c587677a4ce76cfb1f2c7c363493"
|
||||
integrity sha512-s8Cyk6IwptpchPJ1JWYWzy9098BuC+tf24a7O3P6idRjX/C2/GLr+5vifgySk7wji5wwK4LNUmr1SV5H+3bLNw==
|
||||
dependencies:
|
||||
"@nrwl/cli" "13.8.1"
|
||||
"@nrwl/cli" "13.8.5"
|
||||
|
||||
oauth@0.9.x:
|
||||
version "0.9.15"
|
||||
@ -15214,12 +15334,12 @@ pretty-hrtime@^1.0.3:
|
||||
resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1"
|
||||
integrity sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=
|
||||
|
||||
prisma@3.9.1:
|
||||
version "3.9.1"
|
||||
resolved "https://registry.yarnpkg.com/prisma/-/prisma-3.9.1.tgz#7510a8bf06018a5313b9427b1127ce4750b1ce5c"
|
||||
integrity sha512-IGcJAu5LzlFv+i+NNhOEh1J1xVVttsVdRBxmrMN7eIH+7mRN6L89Hz1npUAiz4jOpNlHC7n9QwaOYZGxTqlwQw==
|
||||
prisma@3.10.0:
|
||||
version "3.10.0"
|
||||
resolved "https://registry.yarnpkg.com/prisma/-/prisma-3.10.0.tgz#872d87afbeb1cbcaa77c3d6a63c125e0d704b04d"
|
||||
integrity sha512-dAld12vtwdz9Rz01nOjmnXe+vHana5PSog8t0XGgLemKsUVsaupYpr74AHaS3s78SaTS5s2HOghnJF+jn91ZrA==
|
||||
dependencies:
|
||||
"@prisma/engines" "3.9.0-58.bcc2ff906db47790ee902e7bbc76d7ffb1893009"
|
||||
"@prisma/engines" "3.10.0-50.73e60b76d394f8d37d8ebd1f8918c79029f0db86"
|
||||
|
||||
prismjs@^1.21.0, prismjs@~1.24.0:
|
||||
version "1.24.1"
|
||||
@ -18716,10 +18836,10 @@ y18n@^5.0.5:
|
||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
|
||||
integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
|
||||
|
||||
yahoo-finance2@2.1.9:
|
||||
version "2.1.9"
|
||||
resolved "https://registry.yarnpkg.com/yahoo-finance2/-/yahoo-finance2-2.1.9.tgz#28b157e1cddc5b56e6b354f6b00b453a41bbe8a4"
|
||||
integrity sha512-xLlDqcbK+4Y4oSV7Vq1KcvNcjMuODHQrk2uLyBR4SlXDNjRV7XFpTrwMrDnSLu4pErenj0gXG3ARiCWidFjqzg==
|
||||
yahoo-finance2@2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/yahoo-finance2/-/yahoo-finance2-2.2.0.tgz#8694b04e69f4a79996812b6d082e5b738c51cee6"
|
||||
integrity sha512-ZxLCcoh+J51F7Tol1jpVBmy50IBQSoxsECWYDToBxjZwPloFNHtEVOXNqJlyzTysnzVbPA5TeCNT6G0DoaJnNQ==
|
||||
dependencies:
|
||||
ajv "8.10.0"
|
||||
ajv-formats "2.1.1"
|
||||
|
Reference in New Issue
Block a user