Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror
This commit is contained in:
commit
2725b94169
@ -34,9 +34,11 @@
|
||||
{
|
||||
"files": ["*.ts"],
|
||||
"plugins": ["eslint-plugin-import", "@typescript-eslint"],
|
||||
"extends": ["plugin:@typescript-eslint/recommended-type-checked"],
|
||||
"extends": [
|
||||
"plugin:@typescript-eslint/recommended-type-checked",
|
||||
"plugin:@typescript-eslint/stylistic-type-checked"
|
||||
],
|
||||
"rules": {
|
||||
"@typescript-eslint/consistent-type-definitions": "warn",
|
||||
"@typescript-eslint/dot-notation": "off",
|
||||
"@typescript-eslint/explicit-member-accessibility": [
|
||||
"off",
|
||||
@ -45,8 +47,33 @@
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/member-ordering": "warn",
|
||||
"@typescript-eslint/naming-convention": "off",
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
"@typescript-eslint/naming-convention": [
|
||||
"off",
|
||||
{
|
||||
"selector": "default",
|
||||
"format": ["camelCase"],
|
||||
"leadingUnderscore": "allow",
|
||||
"trailingUnderscore": "allow"
|
||||
},
|
||||
{
|
||||
"selector": ["variable", "classProperty", "typeProperty"],
|
||||
"format": ["camelCase", "UPPER_CASE"],
|
||||
"leadingUnderscore": "allow",
|
||||
"trailingUnderscore": "allow"
|
||||
},
|
||||
{
|
||||
"selector": "objectLiteralProperty",
|
||||
"format": null
|
||||
},
|
||||
{
|
||||
"selector": "enumMember",
|
||||
"format": ["camelCase", "UPPER_CASE", "PascalCase"]
|
||||
},
|
||||
{
|
||||
"selector": "typeLike",
|
||||
"format": ["PascalCase"]
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/no-empty-interface": "warn",
|
||||
"@typescript-eslint/no-inferrable-types": [
|
||||
"warn",
|
||||
@ -61,7 +88,6 @@
|
||||
"hoist": "all"
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/prefer-function-type": "warn",
|
||||
"@typescript-eslint/unified-signatures": "error",
|
||||
"@typescript-eslint/no-loss-of-precision": "warn",
|
||||
"@typescript-eslint/no-var-requires": "warn",
|
||||
@ -109,12 +135,22 @@
|
||||
"@typescript-eslint/no-unsafe-enum-comparison": "warn",
|
||||
"@typescript-eslint/no-unsafe-member-access": "warn",
|
||||
"@typescript-eslint/no-unsafe-return": "warn",
|
||||
"@typescript-eslint/no-unused-vars": "warn",
|
||||
"@typescript-eslint/no-unsafe-call": "warn",
|
||||
"@typescript-eslint/require-await": "warn",
|
||||
"@typescript-eslint/restrict-template-expressions": "warn",
|
||||
"@typescript-eslint/unbound-method": "warn",
|
||||
"prefer-const": "warn"
|
||||
"prefer-const": "warn",
|
||||
|
||||
// The following rules are part of @typescript-eslint/stylistic-type-checked
|
||||
// and can be remove once solved
|
||||
"@typescript-eslint/consistent-type-definitions": "warn",
|
||||
"@typescript-eslint/prefer-function-type": "warn",
|
||||
"@typescript-eslint/no-empty-function": "warn",
|
||||
"@typescript-eslint/prefer-nullish-coalescing": "warn", // TODO: Requires strictNullChecks: true
|
||||
"@typescript-eslint/consistent-type-assertions": "warn",
|
||||
"@typescript-eslint/prefer-optional-chain": "warn",
|
||||
"@typescript-eslint/consistent-indexed-object-style": "warn",
|
||||
"@typescript-eslint/consistent-generic-constructors": "warn"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
3
.github/workflows/build-code.yml
vendored
3
.github/workflows/build-code.yml
vendored
@ -29,6 +29,9 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Check code style
|
||||
run: npm run lint
|
||||
|
||||
- name: Check formatting
|
||||
run: npm run format:check
|
||||
|
||||
|
@ -5,16 +5,19 @@ 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).
|
||||
|
||||
## Unreleased
|
||||
## 2.113.0 - 2024-10-06
|
||||
|
||||
### Added
|
||||
|
||||
- Set up a git-hook via `husky` to lint and format the changes before a commit
|
||||
- Added the `typescript-eslint/recommended-type-checked` rule to the `eslint` configuration
|
||||
- Added the `typescript-eslint/stylistic-type-checked` rule to the `eslint` configuration
|
||||
|
||||
### Changed
|
||||
|
||||
- Optimized the portfolio calculations by reusing date intervals
|
||||
- Refactored the calculation of the allocations by market on the allocations page
|
||||
- Refactored the calculation of the allocations by market on the public page
|
||||
|
||||
### Fixed
|
||||
|
||||
|
@ -74,15 +74,12 @@ export class AccountController {
|
||||
);
|
||||
}
|
||||
|
||||
return this.accountService.deleteAccount(
|
||||
{
|
||||
id_userId: {
|
||||
id,
|
||||
userId: this.request.user.id
|
||||
}
|
||||
},
|
||||
this.request.user.id
|
||||
);
|
||||
return this.accountService.deleteAccount({
|
||||
id_userId: {
|
||||
id,
|
||||
userId: this.request.user.id
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Get()
|
||||
|
@ -108,8 +108,7 @@ export class AccountService {
|
||||
}
|
||||
|
||||
public async deleteAccount(
|
||||
where: Prisma.AccountWhereUniqueInput,
|
||||
aUserId: string
|
||||
where: Prisma.AccountWhereUniqueInput
|
||||
): Promise<Account> {
|
||||
const account = await this.prismaService.account.delete({
|
||||
where
|
||||
@ -170,11 +169,7 @@ export class AccountService {
|
||||
where.isExcluded = false;
|
||||
}
|
||||
|
||||
const {
|
||||
ACCOUNT: filtersByAccount,
|
||||
ASSET_CLASS: filtersByAssetClass,
|
||||
TAG: filtersByTag
|
||||
} = groupBy(filters, ({ type }) => {
|
||||
const { ACCOUNT: filtersByAccount } = groupBy(filters, ({ type }) => {
|
||||
return type;
|
||||
});
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Type } from 'class-transformer';
|
||||
import { ArrayNotEmpty, IsArray, isNotEmptyObject } from 'class-validator';
|
||||
import { ArrayNotEmpty, IsArray } from 'class-validator';
|
||||
|
||||
import { UpdateMarketDataDto } from './update-market-data.dto';
|
||||
|
||||
|
@ -29,8 +29,7 @@ export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
|
||||
token: string,
|
||||
refreshToken: string,
|
||||
profile: Profile,
|
||||
done: Function,
|
||||
done2: Function
|
||||
done: Function
|
||||
) {
|
||||
try {
|
||||
const jwt = await this.authService.validateOAuthLogin({
|
||||
|
@ -57,7 +57,7 @@ export class PublicController {
|
||||
}
|
||||
|
||||
const [
|
||||
{ holdings },
|
||||
{ holdings, markets },
|
||||
{ performance: performance1d },
|
||||
{ performance: performanceMax },
|
||||
{ performance: performanceYtd }
|
||||
@ -76,8 +76,13 @@ export class PublicController {
|
||||
})
|
||||
]);
|
||||
|
||||
Object.values(markets).forEach((market) => {
|
||||
delete market.valueInBaseCurrency;
|
||||
});
|
||||
|
||||
const publicPortfolioResponse: PublicPortfolioResponse = {
|
||||
hasDetails,
|
||||
markets,
|
||||
alias: access.alias,
|
||||
holdings: {},
|
||||
performance: {
|
||||
|
@ -3,24 +3,14 @@ import {
|
||||
AssetProfileIdentifier,
|
||||
SymbolMetrics
|
||||
} from '@ghostfolio/common/interfaces';
|
||||
import { PortfolioSnapshot, TimelinePosition } from '@ghostfolio/common/models';
|
||||
import { PortfolioSnapshot } from '@ghostfolio/common/models';
|
||||
|
||||
export class MWRPortfolioCalculator extends PortfolioCalculator {
|
||||
protected calculateOverallPerformance(
|
||||
positions: TimelinePosition[]
|
||||
): PortfolioSnapshot {
|
||||
protected calculateOverallPerformance(): PortfolioSnapshot {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
protected getSymbolMetrics({
|
||||
dataSource,
|
||||
end,
|
||||
exchangeRates,
|
||||
marketSymbolMap,
|
||||
start,
|
||||
step = 1,
|
||||
symbol
|
||||
}: {
|
||||
protected getSymbolMetrics({}: {
|
||||
end: Date;
|
||||
exchangeRates: { [dateString: string]: number };
|
||||
marketSymbolMap: {
|
||||
|
@ -155,10 +155,27 @@ describe('PortfolioCalculator', () => {
|
||||
dividendInBaseCurrency: new Big('0.62'),
|
||||
fee: new Big('19'),
|
||||
firstBuyDate: '2021-09-16',
|
||||
grossPerformance: new Big('33.25'),
|
||||
grossPerformancePercentage: new Big('0.11136043941322258691'),
|
||||
grossPerformancePercentageWithCurrencyEffect: new Big(
|
||||
'0.11136043941322258691'
|
||||
),
|
||||
grossPerformanceWithCurrencyEffect: new Big('33.25'),
|
||||
investment: new Big('298.58'),
|
||||
investmentWithCurrencyEffect: new Big('298.58'),
|
||||
marketPrice: 331.83,
|
||||
marketPriceInBaseCurrency: 331.83,
|
||||
netPerformance: new Big('14.25'),
|
||||
netPerformancePercentage: new Big('0.04772590260566682296'),
|
||||
netPerformancePercentageWithCurrencyEffectMap: {
|
||||
max: new Big('0.04772590260566682296')
|
||||
},
|
||||
netPerformanceWithCurrencyEffectMap: {
|
||||
'1d': new Big('-5.39'),
|
||||
'5y': new Big('14.25'),
|
||||
max: new Big('14.25'),
|
||||
wtd: new Big('-5.39')
|
||||
},
|
||||
quantity: new Big('1'),
|
||||
symbol: 'MSFT',
|
||||
tags: [],
|
||||
|
@ -1,42 +1,3 @@
|
||||
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
|
||||
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
|
||||
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
|
||||
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
|
||||
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
|
||||
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
|
||||
|
||||
describe('PortfolioCalculator', () => {
|
||||
let configurationService: ConfigurationService;
|
||||
let currentRateService: CurrentRateService;
|
||||
let exchangeRateDataService: ExchangeRateDataService;
|
||||
let portfolioCalculatorFactory: PortfolioCalculatorFactory;
|
||||
let portfolioSnapshotService: PortfolioSnapshotService;
|
||||
let redisCacheService: RedisCacheService;
|
||||
|
||||
beforeEach(() => {
|
||||
configurationService = new ConfigurationService();
|
||||
|
||||
currentRateService = new CurrentRateService(null, null, null, null);
|
||||
|
||||
exchangeRateDataService = new ExchangeRateDataService(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
portfolioSnapshotService = new PortfolioSnapshotService(null);
|
||||
|
||||
redisCacheService = new RedisCacheService(null, null);
|
||||
|
||||
portfolioCalculatorFactory = new PortfolioCalculatorFactory(
|
||||
configurationService,
|
||||
currentRateService,
|
||||
exchangeRateDataService,
|
||||
portfolioSnapshotService,
|
||||
redisCacheService
|
||||
);
|
||||
});
|
||||
|
||||
test.skip('Skip empty test', () => 1);
|
||||
});
|
||||
|
@ -12,13 +12,7 @@ import { DateRange } from '@ghostfolio/common/types';
|
||||
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { Big } from 'big.js';
|
||||
import {
|
||||
addDays,
|
||||
addMilliseconds,
|
||||
differenceInDays,
|
||||
format,
|
||||
isBefore
|
||||
} from 'date-fns';
|
||||
import { addMilliseconds, differenceInDays, format, isBefore } from 'date-fns';
|
||||
import { cloneDeep, first, last, sortBy } from 'lodash';
|
||||
|
||||
export class TWRPortfolioCalculator extends PortfolioCalculator {
|
||||
|
@ -65,6 +65,8 @@ function mockGetValue(symbol: string, date: Date) {
|
||||
return { marketPrice: 89.12 };
|
||||
} else if (isSameDay(parseDate('2021-11-16'), date)) {
|
||||
return { marketPrice: 339.51 };
|
||||
} else if (isSameDay(parseDate('2023-07-09'), date)) {
|
||||
return { marketPrice: 337.22 };
|
||||
} else if (isSameDay(parseDate('2023-07-10'), date)) {
|
||||
return { marketPrice: 331.83 };
|
||||
}
|
||||
|
@ -79,7 +79,7 @@ jest.mock('@ghostfolio/api/services/property/property.service', () => {
|
||||
return {
|
||||
PropertyService: jest.fn().mockImplementation(() => {
|
||||
return {
|
||||
getByKey: (key: string) => Promise.resolve({})
|
||||
getByKey: () => Promise.resolve({})
|
||||
};
|
||||
})
|
||||
};
|
||||
|
@ -13,7 +13,10 @@ import { ApiService } from '@ghostfolio/api/services/api/api.service';
|
||||
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
|
||||
import { ImpersonationService } from '@ghostfolio/api/services/impersonation/impersonation.service';
|
||||
import { getIntervalFromDateRange } from '@ghostfolio/common/calculation-helper';
|
||||
import { HEADER_KEY_IMPERSONATION } from '@ghostfolio/common/config';
|
||||
import {
|
||||
HEADER_KEY_IMPERSONATION,
|
||||
UNKNOWN_KEY
|
||||
} from '@ghostfolio/common/config';
|
||||
import {
|
||||
PortfolioDetails,
|
||||
PortfolioDividends,
|
||||
@ -95,15 +98,22 @@ export class PortfolioController {
|
||||
filterByTags
|
||||
});
|
||||
|
||||
const { accounts, hasErrors, holdings, platforms, summary } =
|
||||
await this.portfolioService.getDetails({
|
||||
dateRange,
|
||||
filters,
|
||||
impersonationId,
|
||||
withMarkets,
|
||||
userId: this.request.user.id,
|
||||
withSummary: true
|
||||
});
|
||||
const {
|
||||
accounts,
|
||||
hasErrors,
|
||||
holdings,
|
||||
markets,
|
||||
marketsAdvanced,
|
||||
platforms,
|
||||
summary
|
||||
} = await this.portfolioService.getDetails({
|
||||
dateRange,
|
||||
filters,
|
||||
impersonationId,
|
||||
withMarkets,
|
||||
userId: this.request.user.id,
|
||||
withSummary: true
|
||||
});
|
||||
|
||||
if (hasErrors || hasNotDefinedValuesInObject(holdings)) {
|
||||
hasError = true;
|
||||
@ -162,6 +172,13 @@ export class PortfolioController {
|
||||
}) ||
|
||||
isRestrictedView(this.request.user)
|
||||
) {
|
||||
Object.values(markets).forEach((market) => {
|
||||
delete market.valueInBaseCurrency;
|
||||
});
|
||||
Object.values(marketsAdvanced).forEach((market) => {
|
||||
delete market.valueInBaseCurrency;
|
||||
});
|
||||
|
||||
portfolioSummary = nullifyValuesInObject(summary, [
|
||||
'cash',
|
||||
'committedFunds',
|
||||
@ -214,6 +231,58 @@ export class PortfolioController {
|
||||
hasError,
|
||||
holdings,
|
||||
platforms,
|
||||
markets: hasDetails
|
||||
? markets
|
||||
: {
|
||||
[UNKNOWN_KEY]: {
|
||||
id: UNKNOWN_KEY,
|
||||
valueInPercentage: 1
|
||||
},
|
||||
developedMarkets: {
|
||||
id: 'developedMarkets',
|
||||
valueInPercentage: 0
|
||||
},
|
||||
emergingMarkets: {
|
||||
id: 'emergingMarkets',
|
||||
valueInPercentage: 0
|
||||
},
|
||||
otherMarkets: {
|
||||
id: 'otherMarkets',
|
||||
valueInPercentage: 0
|
||||
}
|
||||
},
|
||||
marketsAdvanced: hasDetails
|
||||
? marketsAdvanced
|
||||
: {
|
||||
[UNKNOWN_KEY]: {
|
||||
id: UNKNOWN_KEY,
|
||||
valueInPercentage: 0
|
||||
},
|
||||
asiaPacific: {
|
||||
id: 'asiaPacific',
|
||||
valueInPercentage: 0
|
||||
},
|
||||
emergingMarkets: {
|
||||
id: 'emergingMarkets',
|
||||
valueInPercentage: 0
|
||||
},
|
||||
europe: {
|
||||
id: 'europe',
|
||||
valueInPercentage: 0
|
||||
},
|
||||
japan: {
|
||||
id: 'japan',
|
||||
valueInPercentage: 0
|
||||
},
|
||||
northAmerica: {
|
||||
id: 'northAmerica',
|
||||
valueInPercentage: 0
|
||||
},
|
||||
otherMarkets: {
|
||||
id: 'otherMarkets',
|
||||
valueInPercentage: 0
|
||||
}
|
||||
},
|
||||
summary: portfolioSummary
|
||||
};
|
||||
}
|
||||
|
@ -1053,8 +1053,7 @@ export class PortfolioService {
|
||||
dateRange = 'max',
|
||||
filters,
|
||||
impersonationId,
|
||||
userId,
|
||||
withExcludedAccounts = false
|
||||
userId
|
||||
}: {
|
||||
dateRange?: DateRange;
|
||||
filters?: Filter[];
|
||||
@ -1308,7 +1307,7 @@ export class PortfolioService {
|
||||
}
|
||||
};
|
||||
|
||||
for (const [symbol, position] of Object.entries(holdings)) {
|
||||
for (const [, position] of Object.entries(holdings)) {
|
||||
const value = position.valueInBaseCurrency;
|
||||
|
||||
if (position.assetClass !== AssetClass.LIQUIDITY) {
|
||||
|
@ -1,7 +1,5 @@
|
||||
import { Filter } from '@ghostfolio/common/interfaces';
|
||||
|
||||
import { Milliseconds } from 'cache-manager';
|
||||
|
||||
export const RedisCacheServiceMock = {
|
||||
cache: new Map<string, string>(),
|
||||
get: (key: string): Promise<string> => {
|
||||
@ -20,7 +18,7 @@ export const RedisCacheServiceMock = {
|
||||
|
||||
return `portfolio-snapshot-${userId}${filtersHash > 0 ? `-${filtersHash}` : ''}`;
|
||||
},
|
||||
set: (key: string, value: string, ttl?: Milliseconds): Promise<string> => {
|
||||
set: (key: string, value: string): Promise<string> => {
|
||||
RedisCacheServiceMock.cache.set(key, value);
|
||||
|
||||
return Promise.resolve(value);
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { IsCurrencyCode } from '@ghostfolio/api/validators/is-currency-code';
|
||||
import { PortfolioReportRule } from '@ghostfolio/common/interfaces';
|
||||
import type {
|
||||
ColorScheme,
|
||||
DateRange,
|
||||
|
@ -18,7 +18,7 @@ export class EmergencyFundSetup extends Rule<Settings> {
|
||||
this.emergencyFund = emergencyFund;
|
||||
}
|
||||
|
||||
public evaluate(ruleSettings: Settings) {
|
||||
public evaluate() {
|
||||
if (!this.emergencyFund) {
|
||||
return {
|
||||
evaluation: 'No emergency fund has been set up',
|
||||
|
@ -33,7 +33,7 @@ export class AlphaVantageService implements DataProviderInterface {
|
||||
});
|
||||
}
|
||||
|
||||
public canHandle(symbol: string) {
|
||||
public canHandle() {
|
||||
return !!this.configurationService.get('API_KEY_ALPHA_VANTAGE');
|
||||
}
|
||||
|
||||
|
@ -48,7 +48,7 @@ export class CoinGeckoService implements DataProviderInterface {
|
||||
}
|
||||
}
|
||||
|
||||
public canHandle(symbol: string) {
|
||||
public canHandle() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -83,7 +83,6 @@ export class YahooFinanceDataEnhancerService implements DataEnhancerInterface {
|
||||
}
|
||||
|
||||
public async enhance({
|
||||
requestTimeout = this.configurationService.get('REQUEST_TIMEOUT'),
|
||||
response,
|
||||
symbol
|
||||
}: {
|
||||
|
@ -43,7 +43,7 @@ export class EodHistoricalDataService implements DataProviderInterface {
|
||||
this.apiKey = this.configurationService.get('API_KEY_EOD_HISTORICAL_DATA');
|
||||
}
|
||||
|
||||
public canHandle(symbol: string) {
|
||||
public canHandle() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -163,7 +163,7 @@ export class EodHistoricalDataService implements DataProviderInterface {
|
||||
).json<any>();
|
||||
|
||||
return response.reduce(
|
||||
(result, { close, date }, index, array) => {
|
||||
(result, { close, date }) => {
|
||||
if (isNumber(close)) {
|
||||
result[this.convertFromEodSymbol(symbol)][date] = {
|
||||
marketPrice: close
|
||||
|
@ -33,7 +33,7 @@ export class FinancialModelingPrepService implements DataProviderInterface {
|
||||
);
|
||||
}
|
||||
|
||||
public canHandle(symbol: string) {
|
||||
public canHandle() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -29,7 +29,7 @@ export class GoogleSheetsService implements DataProviderInterface {
|
||||
private readonly symbolProfileService: SymbolProfileService
|
||||
) {}
|
||||
|
||||
public canHandle(symbol: string) {
|
||||
public canHandle() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -39,7 +39,7 @@ export class ManualService implements DataProviderInterface {
|
||||
private readonly symbolProfileService: SymbolProfileService
|
||||
) {}
|
||||
|
||||
public canHandle(symbol: string) {
|
||||
public canHandle() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -86,12 +86,8 @@ export class ManualService implements DataProviderInterface {
|
||||
const [symbolProfile] = await this.symbolProfileService.getSymbolProfiles(
|
||||
[{ symbol, dataSource: this.getName() }]
|
||||
);
|
||||
const {
|
||||
defaultMarketPrice,
|
||||
headers = {},
|
||||
selector,
|
||||
url
|
||||
} = symbolProfile?.scraperConfiguration ?? {};
|
||||
const { defaultMarketPrice, selector, url } =
|
||||
symbolProfile?.scraperConfiguration ?? {};
|
||||
|
||||
if (defaultMarketPrice) {
|
||||
const historical: {
|
||||
|
@ -26,7 +26,7 @@ export class RapidApiService implements DataProviderInterface {
|
||||
private readonly configurationService: ConfigurationService
|
||||
) {}
|
||||
|
||||
public canHandle(symbol: string) {
|
||||
public canHandle() {
|
||||
return !!this.configurationService.get('API_KEY_RAPID_API');
|
||||
}
|
||||
|
||||
|
@ -34,7 +34,7 @@ export class YahooFinanceService implements DataProviderInterface {
|
||||
private readonly yahooFinanceDataEnhancerService: YahooFinanceDataEnhancerService
|
||||
) {}
|
||||
|
||||
public canHandle(symbol: string) {
|
||||
public canHandle() {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,5 @@
|
||||
export const ExchangeRateDataServiceMock = {
|
||||
getExchangeRatesByCurrency: ({
|
||||
currencies,
|
||||
endDate,
|
||||
startDate,
|
||||
targetCurrency
|
||||
}): Promise<any> => {
|
||||
getExchangeRatesByCurrency: ({ targetCurrency }): Promise<any> => {
|
||||
if (targetCurrency === 'CHF') {
|
||||
return Promise.resolve({
|
||||
CHFCHF: {
|
||||
|
@ -5,8 +5,6 @@ import { IPortfolioSnapshotQueueJob } from './interfaces/portfolio-snapshot-queu
|
||||
|
||||
export const PortfolioSnapshotServiceMock = {
|
||||
addJobToQueue({
|
||||
data,
|
||||
name,
|
||||
opts
|
||||
}: {
|
||||
data: IPortfolioSnapshotQueueJob;
|
||||
|
@ -4,8 +4,7 @@ import {
|
||||
registerDecorator,
|
||||
ValidationOptions,
|
||||
ValidatorConstraint,
|
||||
ValidatorConstraintInterface,
|
||||
ValidationArguments
|
||||
ValidatorConstraintInterface
|
||||
} from 'class-validator';
|
||||
import { isISO4217CurrencyCode } from 'class-validator';
|
||||
|
||||
@ -25,7 +24,7 @@ export function IsCurrencyCode(validationOptions?: ValidationOptions) {
|
||||
export class IsExtendedCurrencyConstraint
|
||||
implements ValidatorConstraintInterface
|
||||
{
|
||||
public defaultMessage(args: ValidationArguments) {
|
||||
public defaultMessage() {
|
||||
return '$value must be a valid ISO4217 currency code';
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,7 @@ export class CustomDateAdapter extends NativeDateAdapter {
|
||||
/**
|
||||
* Formats a date as a string
|
||||
*/
|
||||
public format(aDate: Date, aParseFormat: string): string {
|
||||
public format(aDate: Date): string {
|
||||
return format(aDate, getDateFormatString(this.locale));
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,7 @@ import {
|
||||
} from '@ghostfolio/common/interfaces';
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { EMPTY, catchError, finalize, forkJoin, takeUntil } from 'rxjs';
|
||||
import { EMPTY, catchError, finalize, forkJoin } from 'rxjs';
|
||||
|
||||
@Injectable()
|
||||
export class AdminMarketDataService {
|
||||
|
@ -11,7 +11,6 @@ import {
|
||||
User
|
||||
} from '@ghostfolio/common/interfaces';
|
||||
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||
import { DateRange } from '@ghostfolio/common/types';
|
||||
|
||||
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { DeviceDetectorService } from 'ngx-device-detector';
|
||||
|
@ -33,7 +33,7 @@ export class NotificationService {
|
||||
title: aParams.title
|
||||
});
|
||||
|
||||
return dialog.afterClosed().subscribe((result) => {
|
||||
return dialog.afterClosed().subscribe(() => {
|
||||
if (isFunction(aParams.discardFn)) {
|
||||
aParams.discardFn();
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||
import { TabConfiguration, User } from '@ghostfolio/common/interfaces';
|
||||
import { TabConfiguration } from '@ghostfolio/common/interfaces';
|
||||
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
|
@ -47,7 +47,7 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
|
||||
public hasImpersonationId: boolean;
|
||||
public isLoading = false;
|
||||
public markets: {
|
||||
[key in Market]: { name: string; value: number };
|
||||
[key in Market]: { id: Market; valueInPercentage: number };
|
||||
};
|
||||
public marketsAdvanced: {
|
||||
[key in MarketAdvanced]: {
|
||||
@ -219,24 +219,6 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
|
||||
value: 0
|
||||
}
|
||||
};
|
||||
this.markets = {
|
||||
[UNKNOWN_KEY]: {
|
||||
name: UNKNOWN_KEY,
|
||||
value: 0
|
||||
},
|
||||
developedMarkets: {
|
||||
name: 'developedMarkets',
|
||||
value: 0
|
||||
},
|
||||
emergingMarkets: {
|
||||
name: 'emergingMarkets',
|
||||
value: 0
|
||||
},
|
||||
otherMarkets: {
|
||||
name: 'otherMarkets',
|
||||
value: 0
|
||||
}
|
||||
};
|
||||
this.marketsAdvanced = {
|
||||
[UNKNOWN_KEY]: {
|
||||
id: UNKNOWN_KEY,
|
||||
@ -318,6 +300,16 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
|
||||
};
|
||||
}
|
||||
|
||||
this.markets = this.portfolioDetails.markets;
|
||||
|
||||
Object.values(this.portfolioDetails.marketsAdvanced).forEach(
|
||||
({ id, valueInBaseCurrency, valueInPercentage }) => {
|
||||
this.marketsAdvanced[id].value = isNumber(valueInBaseCurrency)
|
||||
? valueInBaseCurrency
|
||||
: valueInPercentage;
|
||||
}
|
||||
);
|
||||
|
||||
for (const [symbol, position] of Object.entries(
|
||||
this.portfolioDetails.holdings
|
||||
)) {
|
||||
@ -348,48 +340,6 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
|
||||
// Prepare analysis data by continents, countries, holdings and sectors except for liquidity
|
||||
|
||||
if (position.countries.length > 0) {
|
||||
this.markets.developedMarkets.value +=
|
||||
position.markets.developedMarkets *
|
||||
(isNumber(position.valueInBaseCurrency)
|
||||
? position.valueInBaseCurrency
|
||||
: position.valueInPercentage);
|
||||
this.markets.emergingMarkets.value +=
|
||||
position.markets.emergingMarkets *
|
||||
(isNumber(position.valueInBaseCurrency)
|
||||
? position.valueInBaseCurrency
|
||||
: position.valueInPercentage);
|
||||
this.markets.otherMarkets.value +=
|
||||
position.markets.otherMarkets *
|
||||
(isNumber(position.valueInBaseCurrency)
|
||||
? position.valueInBaseCurrency
|
||||
: position.valueInPercentage);
|
||||
|
||||
this.marketsAdvanced.asiaPacific.value +=
|
||||
position.marketsAdvanced.asiaPacific *
|
||||
(isNumber(position.valueInBaseCurrency)
|
||||
? position.valueInBaseCurrency
|
||||
: position.valueInPercentage);
|
||||
this.marketsAdvanced.emergingMarkets.value +=
|
||||
position.marketsAdvanced.emergingMarkets *
|
||||
(isNumber(position.valueInBaseCurrency)
|
||||
? position.valueInBaseCurrency
|
||||
: position.valueInPercentage);
|
||||
this.marketsAdvanced.europe.value +=
|
||||
position.marketsAdvanced.europe *
|
||||
(isNumber(position.valueInBaseCurrency)
|
||||
? position.valueInBaseCurrency
|
||||
: position.valueInPercentage);
|
||||
this.marketsAdvanced.japan.value +=
|
||||
position.marketsAdvanced.japan *
|
||||
(isNumber(position.valueInBaseCurrency)
|
||||
? position.valueInBaseCurrency
|
||||
: position.valueInPercentage);
|
||||
this.marketsAdvanced.northAmerica.value +=
|
||||
position.marketsAdvanced.northAmerica *
|
||||
(isNumber(position.valueInBaseCurrency)
|
||||
? position.valueInBaseCurrency
|
||||
: position.valueInPercentage);
|
||||
|
||||
for (const country of position.countries) {
|
||||
const { code, continent, name, weight } = country;
|
||||
|
||||
@ -439,18 +389,6 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
|
||||
)
|
||||
? this.portfolioDetails.holdings[symbol].valueInBaseCurrency
|
||||
: this.portfolioDetails.holdings[symbol].valueInPercentage;
|
||||
|
||||
this.markets[UNKNOWN_KEY].value += isNumber(
|
||||
position.valueInBaseCurrency
|
||||
)
|
||||
? this.portfolioDetails.holdings[symbol].valueInBaseCurrency
|
||||
: this.portfolioDetails.holdings[symbol].valueInPercentage;
|
||||
|
||||
this.marketsAdvanced[UNKNOWN_KEY].value += isNumber(
|
||||
position.valueInBaseCurrency
|
||||
)
|
||||
? this.portfolioDetails.holdings[symbol].valueInBaseCurrency
|
||||
: this.portfolioDetails.holdings[symbol].valueInPercentage;
|
||||
}
|
||||
|
||||
if (position.holdings.length > 0) {
|
||||
@ -538,21 +476,6 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
|
||||
};
|
||||
}
|
||||
|
||||
const marketsTotal =
|
||||
this.markets.developedMarkets.value +
|
||||
this.markets.emergingMarkets.value +
|
||||
this.markets.otherMarkets.value +
|
||||
this.markets[UNKNOWN_KEY].value;
|
||||
|
||||
this.markets.developedMarkets.value =
|
||||
this.markets.developedMarkets.value / marketsTotal;
|
||||
this.markets.emergingMarkets.value =
|
||||
this.markets.emergingMarkets.value / marketsTotal;
|
||||
this.markets.otherMarkets.value =
|
||||
this.markets.otherMarkets.value / marketsTotal;
|
||||
this.markets[UNKNOWN_KEY].value =
|
||||
this.markets[UNKNOWN_KEY].value / marketsTotal;
|
||||
|
||||
this.topHoldings = Object.values(this.topHoldingsMap)
|
||||
.map(({ name, value }) => {
|
||||
if (this.hasImpersonationId || this.user.settings.isRestrictedView) {
|
||||
|
@ -218,7 +218,7 @@
|
||||
i18n
|
||||
size="large"
|
||||
[isPercent]="true"
|
||||
[value]="markets?.developedMarkets?.value"
|
||||
[value]="markets?.developedMarkets?.valueInPercentage"
|
||||
>Developed Markets</gf-value
|
||||
>
|
||||
</div>
|
||||
@ -227,7 +227,7 @@
|
||||
i18n
|
||||
size="large"
|
||||
[isPercent]="true"
|
||||
[value]="markets?.emergingMarkets?.value"
|
||||
[value]="markets?.emergingMarkets?.valueInPercentage"
|
||||
>Emerging Markets</gf-value
|
||||
>
|
||||
</div>
|
||||
@ -236,17 +236,17 @@
|
||||
i18n
|
||||
size="large"
|
||||
[isPercent]="true"
|
||||
[value]="markets?.otherMarkets?.value"
|
||||
[value]="markets?.otherMarkets?.valueInPercentage"
|
||||
>Other Markets</gf-value
|
||||
>
|
||||
</div>
|
||||
@if (markets?.[UNKNOWN_KEY]?.value > 0) {
|
||||
@if (markets?.[UNKNOWN_KEY]?.valueInPercentage > 0) {
|
||||
<div class="col-xs-12 col-md my-2">
|
||||
<gf-value
|
||||
i18n
|
||||
size="large"
|
||||
[isPercent]="true"
|
||||
[value]="markets?.[UNKNOWN_KEY]?.value"
|
||||
[value]="markets?.[UNKNOWN_KEY]?.valueInPercentage"
|
||||
>No data available</gf-value
|
||||
>
|
||||
</div>
|
||||
|
@ -32,7 +32,7 @@ export class PublicPageComponent implements OnInit {
|
||||
public deviceType: string;
|
||||
public holdings: PublicPortfolioResponse['holdings'][string][];
|
||||
public markets: {
|
||||
[key in Market]: { name: string; value: number };
|
||||
[key in Market]: { id: Market; valueInPercentage: number };
|
||||
};
|
||||
public positions: {
|
||||
[symbol: string]: Pick<PortfolioPosition, 'currency' | 'name'> & {
|
||||
@ -102,24 +102,7 @@ export class PublicPageComponent implements OnInit {
|
||||
}
|
||||
};
|
||||
this.holdings = [];
|
||||
this.markets = {
|
||||
[UNKNOWN_KEY]: {
|
||||
name: UNKNOWN_KEY,
|
||||
value: 0
|
||||
},
|
||||
developedMarkets: {
|
||||
name: 'developedMarkets',
|
||||
value: 0
|
||||
},
|
||||
emergingMarkets: {
|
||||
name: 'emergingMarkets',
|
||||
value: 0
|
||||
},
|
||||
otherMarkets: {
|
||||
name: 'otherMarkets',
|
||||
value: 0
|
||||
}
|
||||
};
|
||||
this.markets = this.publicPortfolioDetails.markets;
|
||||
this.positions = {};
|
||||
this.sectors = {
|
||||
[UNKNOWN_KEY]: {
|
||||
@ -150,13 +133,6 @@ export class PublicPageComponent implements OnInit {
|
||||
// Prepare analysis data by continents, countries, holdings and sectors except for liquidity
|
||||
|
||||
if (position.countries.length > 0) {
|
||||
this.markets.developedMarkets.value +=
|
||||
position.markets.developedMarkets * position.valueInBaseCurrency;
|
||||
this.markets.emergingMarkets.value +=
|
||||
position.markets.emergingMarkets * position.valueInBaseCurrency;
|
||||
this.markets.otherMarkets.value +=
|
||||
position.markets.otherMarkets * position.valueInBaseCurrency;
|
||||
|
||||
for (const country of position.countries) {
|
||||
const { code, continent, name, weight } = country;
|
||||
|
||||
@ -192,9 +168,6 @@ export class PublicPageComponent implements OnInit {
|
||||
|
||||
this.countries[UNKNOWN_KEY].value +=
|
||||
this.publicPortfolioDetails.holdings[symbol].valueInBaseCurrency;
|
||||
|
||||
this.markets[UNKNOWN_KEY].value +=
|
||||
this.publicPortfolioDetails.holdings[symbol].valueInBaseCurrency;
|
||||
}
|
||||
|
||||
if (position.sectors.length > 0) {
|
||||
@ -227,21 +200,6 @@ export class PublicPageComponent implements OnInit {
|
||||
: position.valueInPercentage
|
||||
};
|
||||
}
|
||||
|
||||
const marketsTotal =
|
||||
this.markets.developedMarkets.value +
|
||||
this.markets.emergingMarkets.value +
|
||||
this.markets.otherMarkets.value +
|
||||
this.markets[UNKNOWN_KEY].value;
|
||||
|
||||
this.markets.developedMarkets.value =
|
||||
this.markets.developedMarkets.value / marketsTotal;
|
||||
this.markets.emergingMarkets.value =
|
||||
this.markets.emergingMarkets.value / marketsTotal;
|
||||
this.markets.otherMarkets.value =
|
||||
this.markets.otherMarkets.value / marketsTotal;
|
||||
this.markets[UNKNOWN_KEY].value =
|
||||
this.markets[UNKNOWN_KEY].value / marketsTotal;
|
||||
}
|
||||
|
||||
public ngOnDestroy() {
|
||||
|
@ -156,7 +156,7 @@
|
||||
i18n
|
||||
size="large"
|
||||
[isPercent]="true"
|
||||
[value]="markets?.developedMarkets?.value"
|
||||
[value]="markets?.developedMarkets?.valueInPercentage"
|
||||
>Developed Markets</gf-value
|
||||
>
|
||||
</div>
|
||||
@ -165,7 +165,7 @@
|
||||
i18n
|
||||
size="large"
|
||||
[isPercent]="true"
|
||||
[value]="markets?.emergingMarkets?.value"
|
||||
[value]="markets?.emergingMarkets?.valueInPercentage"
|
||||
>Emerging Markets</gf-value
|
||||
>
|
||||
</div>
|
||||
@ -174,17 +174,17 @@
|
||||
i18n
|
||||
size="large"
|
||||
[isPercent]="true"
|
||||
[value]="markets?.otherMarkets?.value"
|
||||
[value]="markets?.otherMarkets?.valueInPercentage"
|
||||
>Other Markets</gf-value
|
||||
>
|
||||
</div>
|
||||
@if (markets?.[UNKNOWN_KEY]?.value > 0) {
|
||||
@if (markets?.[UNKNOWN_KEY]?.valueInPercentage > 0) {
|
||||
<div class="col-xs-12 col-md my-2">
|
||||
<gf-value
|
||||
i18n
|
||||
size="large"
|
||||
[isPercent]="true"
|
||||
[value]="markets?.[UNKNOWN_KEY]?.value"
|
||||
[value]="markets?.[UNKNOWN_KEY]?.valueInPercentage"
|
||||
>No data available</gf-value
|
||||
>
|
||||
</div>
|
||||
|
@ -15,6 +15,8 @@
|
||||
],
|
||||
"angularCompilerOptions": {
|
||||
"strictInjectionParameters": true,
|
||||
// TODO: Enable stricter rules for this project
|
||||
"strictInputAccessModifiers": false,
|
||||
"strictTemplates": false
|
||||
},
|
||||
"compilerOptions": {
|
||||
|
@ -18,14 +18,14 @@ export interface PortfolioDetails {
|
||||
markets?: {
|
||||
[key in Market]: {
|
||||
id: Market;
|
||||
valueInBaseCurrency: number;
|
||||
valueInBaseCurrency?: number;
|
||||
valueInPercentage: number;
|
||||
};
|
||||
};
|
||||
marketsAdvanced?: {
|
||||
[key in MarketAdvanced]: {
|
||||
id: MarketAdvanced;
|
||||
valueInBaseCurrency: number;
|
||||
valueInBaseCurrency?: number;
|
||||
valueInPercentage: number;
|
||||
};
|
||||
};
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { PortfolioPosition } from '../portfolio-position.interface';
|
||||
import { PortfolioDetails, PortfolioPosition } from '..';
|
||||
import { Market } from '../../types';
|
||||
|
||||
export interface PublicPortfolioResponse extends PublicPortfolioResponseV1 {
|
||||
alias?: string;
|
||||
@ -22,6 +23,12 @@ export interface PublicPortfolioResponse extends PublicPortfolioResponseV1 {
|
||||
| 'valueInPercentage'
|
||||
>;
|
||||
};
|
||||
markets: {
|
||||
[key in Market]: Pick<
|
||||
PortfolioDetails['markets'][key],
|
||||
'id' | 'valueInPercentage'
|
||||
>;
|
||||
};
|
||||
}
|
||||
|
||||
interface PublicPortfolioResponseV1 {
|
||||
|
@ -36,7 +36,6 @@ import { MatMenuTrigger } from '@angular/material/menu';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { Account, AssetClass } from '@prisma/client';
|
||||
import { eachYearOfInterval, format } from 'date-fns';
|
||||
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
||||
import { EMPTY, Observable, Subject, lastValueFrom } from 'rxjs';
|
||||
import {
|
||||
|
@ -18,7 +18,6 @@ import {
|
||||
Input,
|
||||
OnChanges,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
Output,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
|
@ -270,7 +270,7 @@ export class GfPortfolioProportionChartComponent
|
||||
}
|
||||
];
|
||||
|
||||
let labels = chartDataSorted.map(([symbol, { name }]) => {
|
||||
let labels = chartDataSorted.map(([, { name }]) => {
|
||||
return name;
|
||||
});
|
||||
|
||||
|
@ -10,7 +10,6 @@ import {
|
||||
Input,
|
||||
OnChanges,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
|
@ -14,11 +14,11 @@
|
||||
}
|
||||
],
|
||||
"compilerOptions": {
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"target": "es2020",
|
||||
// TODO: Remove once solved in tsconfig.base.json
|
||||
"strict": false,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"target": "es2020"
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"strictInjectionParameters": true,
|
||||
|
8
package-lock.json
generated
8
package-lock.json
generated
@ -84,7 +84,6 @@
|
||||
"passport": "0.7.0",
|
||||
"passport-google-oauth20": "2.0.0",
|
||||
"passport-jwt": "4.0.1",
|
||||
"prisma": "5.20.0",
|
||||
"reflect-metadata": "0.1.13",
|
||||
"rxjs": "7.5.6",
|
||||
"stripe": "15.11.0",
|
||||
@ -150,6 +149,7 @@
|
||||
"nx": "19.5.6",
|
||||
"prettier": "3.3.3",
|
||||
"prettier-plugin-organize-attributes": "1.0.0",
|
||||
"prisma": "5.20.0",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"replace-in-file": "7.0.1",
|
||||
@ -9668,12 +9668,14 @@
|
||||
"version": "5.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.20.0.tgz",
|
||||
"integrity": "sha512-oCx79MJ4HSujokA8S1g0xgZUGybD4SyIOydoHMngFYiwEwYDQ5tBQkK5XoEHuwOYDKUOKRn/J0MEymckc4IgsQ==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@prisma/engines": {
|
||||
"version": "5.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.20.0.tgz",
|
||||
"integrity": "sha512-DtqkP+hcZvPEbj8t8dK5df2b7d3B8GNauKqaddRRqQBBlgkbdhJkxhoJTrOowlS3vaRt2iMCkU0+CSNn0KhqAQ==",
|
||||
"devOptional": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
@ -9687,12 +9689,14 @@
|
||||
"version": "5.20.0-12.06fc58a368dc7be9fbbbe894adf8d445d208c284",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.20.0-12.06fc58a368dc7be9fbbbe894adf8d445d208c284.tgz",
|
||||
"integrity": "sha512-Lg8AS5lpi0auZe2Mn4gjuCg081UZf88k3cn0RCwHgR+6cyHHpttPZBElJTHf83ZGsRNAmVCZCfUGA57WB4u4JA==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@prisma/fetch-engine": {
|
||||
"version": "5.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.20.0.tgz",
|
||||
"integrity": "sha512-JVcaPXC940wOGpCOwuqQRTz6I9SaBK0c1BAyC1pcz9xBi+dzFgUu3G/p9GV1FhFs9OKpfSpIhQfUJE9y00zhqw==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@prisma/debug": "5.20.0",
|
||||
@ -9704,6 +9708,7 @@
|
||||
"version": "5.20.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.20.0.tgz",
|
||||
"integrity": "sha512-8/+CehTZZNzJlvuryRgc77hZCWrUDYd/PmlZ7p2yNXtmf2Una4BWnTbak3us6WVdqoz5wmptk6IhsXdG2v5fmA==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@prisma/debug": "5.20.0"
|
||||
@ -28839,6 +28844,7 @@
|
||||
"version": "5.20.0",
|
||||
"resolved": "https://registry.npmjs.org/prisma/-/prisma-5.20.0.tgz",
|
||||
"integrity": "sha512-6obb3ucKgAnsGS9x9gLOe8qa51XxvJ3vLQtmyf52CTey1Qcez3A6W6ROH5HIz5Q5bW+0VpmZb8WBohieMFGpig==",
|
||||
"devOptional": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ghostfolio",
|
||||
"version": "2.112.0",
|
||||
"version": "2.113.0",
|
||||
"homepage": "https://ghostfol.io",
|
||||
"license": "AGPL-3.0",
|
||||
"repository": "https://github.com/ghostfolio/ghostfolio",
|
||||
@ -130,7 +130,6 @@
|
||||
"passport": "0.7.0",
|
||||
"passport-google-oauth20": "2.0.0",
|
||||
"passport-jwt": "4.0.1",
|
||||
"prisma": "5.20.0",
|
||||
"reflect-metadata": "0.1.13",
|
||||
"rxjs": "7.5.6",
|
||||
"stripe": "15.11.0",
|
||||
@ -196,6 +195,7 @@
|
||||
"nx": "19.5.6",
|
||||
"prettier": "3.3.3",
|
||||
"prettier-plugin-organize-attributes": "1.0.0",
|
||||
"prisma": "5.20.0",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"replace-in-file": "7.0.1",
|
||||
|
@ -20,7 +20,20 @@
|
||||
"@ghostfolio/client/*": ["apps/client/src/app/*"],
|
||||
"@ghostfolio/common/*": ["libs/common/src/lib/*"],
|
||||
"@ghostfolio/ui/*": ["libs/ui/src/lib/*"]
|
||||
}
|
||||
},
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": false,
|
||||
"strictNullChecks": false,
|
||||
"strictPropertyInitialization": false,
|
||||
"noImplicitReturns": false,
|
||||
"noImplicitAny": false,
|
||||
"noImplicitThis": false,
|
||||
"noImplicitOverride": false,
|
||||
"noPropertyAccessFromIndexSignature": false,
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"allowUnreachableCode": true
|
||||
},
|
||||
"exclude": ["node_modules", "tmp"]
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user