Browse Source

Merge remote-tracking branch 'origin/main' into feature/extend-holdings-endpoint-for-cash

pull/5650/head
KenTandrian 1 week ago
parent
commit
b2efcced89
  1. 14
      CHANGELOG.md
  2. 36
      apps/api/src/services/data-provider/coingecko/coingecko.service.ts
  3. 45
      apps/api/src/services/data-provider/eod-historical-data/eod-historical-data.service.ts
  4. 87
      apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts
  5. 34
      apps/api/src/services/data-provider/ghostfolio/ghostfolio.service.ts
  6. 2
      apps/client/src/app/components/access-table/access-table.component.html
  7. 6
      apps/client/src/app/components/admin-jobs/admin-jobs.html
  8. 2
      apps/client/src/app/components/admin-market-data/admin-market-data.html
  9. 2
      apps/client/src/app/components/admin-platform/admin-platform.component.html
  10. 2
      apps/client/src/app/components/admin-tag/admin-tag.component.html
  11. 4
      apps/client/src/app/components/admin-users/admin-users.html
  12. 6
      libs/ui/src/lib/accounts-table/accounts-table.component.html
  13. 18
      libs/ui/src/lib/activities-table/activities-table.component.html
  14. 46
      libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.stories.ts
  15. 20
      package-lock.json
  16. 6
      package.json

14
CHANGELOG.md

@ -13,10 +13,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Refactored the API query parameters in various data provider services
- Extended the _Storybook_ stories of the portfolio proportion chart component by a story using percentage values
- Upgraded `@internationalized/number` from version `3.6.3` to `3.6.5`
- Upgraded `prettier` from version `3.7.2` to `3.7.3`
### Fixed
- Improved the country weightings in the _Financial Modeling Prep_ service
## 2.220.0 - 2025-11-29
### Changed
- Restricted the asset profile data gathering on Sundays to only process outdated asset profiles
- Removed the _Cypress_ testing setup
- Eliminated `uuid` in favor of using `randomUUID` from `node:crypto`
- Upgraded `color` from version `5.0.0` to `5.0.3`
- Upgraded `prettier` from version `3.6.2` to `3.7.2`
### Fixed

36
apps/api/src/services/data-provider/coingecko/coingecko.service.ts

@ -110,12 +110,14 @@ export class CoinGeckoService implements DataProviderInterface {
[symbol: string]: { [date: string]: DataProviderHistoricalResponse };
}> {
try {
const queryParams = new URLSearchParams({
from: getUnixTime(from).toString(),
to: getUnixTime(to).toString(),
vs_currency: DEFAULT_CURRENCY.toLowerCase()
});
const { error, prices, status } = await fetch(
`${
this.apiUrl
}/coins/${symbol}/market_chart/range?vs_currency=${DEFAULT_CURRENCY.toLowerCase()}&from=${getUnixTime(
from
)}&to=${getUnixTime(to)}`,
`${this.apiUrl}/coins/${symbol}/market_chart/range?${queryParams.toString()}`,
{
headers: this.headers,
signal: AbortSignal.timeout(requestTimeout)
@ -172,10 +174,13 @@ export class CoinGeckoService implements DataProviderInterface {
}
try {
const queryParams = new URLSearchParams({
ids: symbols.join(','),
vs_currencies: DEFAULT_CURRENCY.toLowerCase()
});
const quotes = await fetch(
`${this.apiUrl}/simple/price?ids=${symbols.join(
','
)}&vs_currencies=${DEFAULT_CURRENCY.toLowerCase()}`,
`${this.apiUrl}/simple/price?${queryParams.toString()}`,
{
headers: this.headers,
signal: AbortSignal.timeout(requestTimeout)
@ -219,10 +224,17 @@ export class CoinGeckoService implements DataProviderInterface {
let items: LookupItem[] = [];
try {
const { coins } = await fetch(`${this.apiUrl}/search?query=${query}`, {
headers: this.headers,
signal: AbortSignal.timeout(requestTimeout)
}).then((res) => res.json());
const queryParams = new URLSearchParams({
query
});
const { coins } = await fetch(
`${this.apiUrl}/search?${queryParams.toString()}`,
{
headers: this.headers,
signal: AbortSignal.timeout(requestTimeout)
}
).then((res) => res.json());
items = coins.map(({ id: symbol, name }) => {
return {

45
apps/api/src/services/data-provider/eod-historical-data/eod-historical-data.service.ts

@ -96,17 +96,19 @@ export class EodHistoricalDataService implements DataProviderInterface {
}
try {
const queryParams = new URLSearchParams({
api_token: this.apiKey,
fmt: 'json',
from: format(from, DATE_FORMAT),
to: format(to, DATE_FORMAT)
});
const response: {
[date: string]: DataProviderHistoricalResponse;
} = {};
const historicalResult = await fetch(
`${this.URL}/div/${symbol}?api_token=${
this.apiKey
}&fmt=json&from=${format(from, DATE_FORMAT)}&to=${format(
to,
DATE_FORMAT
)}`,
`${this.URL}/div/${symbol}?${queryParams.toString()}`,
{
signal: AbortSignal.timeout(requestTimeout)
}
@ -144,13 +146,16 @@ export class EodHistoricalDataService implements DataProviderInterface {
symbol = this.convertToEodSymbol(symbol);
try {
const queryParams = new URLSearchParams({
api_token: this.apiKey,
fmt: 'json',
from: format(from, DATE_FORMAT),
period: granularity,
to: format(to, DATE_FORMAT)
});
const response = await fetch(
`${this.URL}/eod/${symbol}?api_token=${
this.apiKey
}&fmt=json&from=${format(from, DATE_FORMAT)}&to=${format(
to,
DATE_FORMAT
)}&period=${granularity}`,
`${this.URL}/eod/${symbol}?${queryParams.toString()}`,
{
signal: AbortSignal.timeout(requestTimeout)
}
@ -208,10 +213,14 @@ export class EodHistoricalDataService implements DataProviderInterface {
});
try {
const queryParams = new URLSearchParams({
api_token: this.apiKey,
fmt: 'json',
s: eodHistoricalDataSymbols.join(',')
});
const realTimeResponse = await fetch(
`${this.URL}/real-time/${eodHistoricalDataSymbols[0]}?api_token=${
this.apiKey
}&fmt=json&s=${eodHistoricalDataSymbols.join(',')}`,
`${this.URL}/real-time/${eodHistoricalDataSymbols[0]}?${queryParams.toString()}`,
{
signal: AbortSignal.timeout(requestTimeout)
}
@ -413,8 +422,12 @@ export class EodHistoricalDataService implements DataProviderInterface {
})[] = [];
try {
const queryParams = new URLSearchParams({
api_token: this.apiKey
});
const response = await fetch(
`${this.URL}/search/${query}?api_token=${this.apiKey}`,
`${this.URL}/search/${query}?${queryParams.toString()}`,
{
signal: AbortSignal.timeout(requestTimeout)
}

87
apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts

@ -44,6 +44,12 @@ import {
@Injectable()
export class FinancialModelingPrepService implements DataProviderInterface {
private static countriesMapping = {
'Korea (the Republic of)': 'South Korea',
'Russian Federation': 'Russia',
'Taiwan (Province of China)': 'Taiwan'
};
private apiKey: string;
public constructor(
@ -79,8 +85,13 @@ export class FinancialModelingPrepService implements DataProviderInterface {
symbol.length - DEFAULT_CURRENCY.length
);
} else if (this.cryptocurrencyService.isCryptocurrency(symbol)) {
const queryParams = new URLSearchParams({
symbol,
apikey: this.apiKey
});
const [quote] = await fetch(
`${this.getUrl({ version: 'stable' })}/quote?symbol=${symbol}&apikey=${this.apiKey}`,
`${this.getUrl({ version: 'stable' })}/quote?${queryParams.toString()}`,
{
signal: AbortSignal.timeout(requestTimeout)
}
@ -93,8 +104,13 @@ export class FinancialModelingPrepService implements DataProviderInterface {
);
response.name = quote.name;
} else {
const queryParams = new URLSearchParams({
symbol,
apikey: this.apiKey
});
const [assetProfile] = await fetch(
`${this.getUrl({ version: 'stable' })}/profile?symbol=${symbol}&apikey=${this.apiKey}`,
`${this.getUrl({ version: 'stable' })}/profile?${queryParams.toString()}`,
{
signal: AbortSignal.timeout(requestTimeout)
}
@ -114,19 +130,31 @@ export class FinancialModelingPrepService implements DataProviderInterface {
assetSubClass === AssetSubClass.ETF ||
assetSubClass === AssetSubClass.MUTUALFUND
) {
const queryParams = new URLSearchParams({
symbol,
apikey: this.apiKey
});
const etfCountryWeightings = await fetch(
`${this.getUrl({ version: 'stable' })}/etf/country-weightings?symbol=${symbol}&apikey=${this.apiKey}`,
`${this.getUrl({ version: 'stable' })}/etf/country-weightings?${queryParams.toString()}`,
{
signal: AbortSignal.timeout(requestTimeout)
}
).then((res) => res.json());
response.countries = etfCountryWeightings.map(
({ country: countryName, weightPercentage }) => {
response.countries = etfCountryWeightings
.filter(({ country: countryName }) => {
return countryName.toLowerCase() !== 'other';
})
.map(({ country: countryName, weightPercentage }) => {
let countryCode: string;
for (const [code, country] of Object.entries(countries)) {
if (country.name === countryName) {
if (
country.name === countryName ||
country.name ===
FinancialModelingPrepService.countriesMapping[countryName]
) {
countryCode = code;
break;
}
@ -136,11 +164,10 @@ export class FinancialModelingPrepService implements DataProviderInterface {
code: countryCode,
weight: parseFloat(weightPercentage.slice(0, -1)) / 100
};
}
);
});
const etfHoldings = await fetch(
`${this.getUrl({ version: 'stable' })}/etf/holdings?symbol=${symbol}&apikey=${this.apiKey}`,
`${this.getUrl({ version: 'stable' })}/etf/holdings?${queryParams.toString()}`,
{
signal: AbortSignal.timeout(requestTimeout)
}
@ -159,7 +186,7 @@ export class FinancialModelingPrepService implements DataProviderInterface {
);
const [etfInformation] = await fetch(
`${this.getUrl({ version: 'stable' })}/etf/info?symbol=${symbol}&apikey=${this.apiKey}`,
`${this.getUrl({ version: 'stable' })}/etf/info?${queryParams.toString()}`,
{
signal: AbortSignal.timeout(requestTimeout)
}
@ -170,7 +197,7 @@ export class FinancialModelingPrepService implements DataProviderInterface {
}
const etfSectorWeightings = await fetch(
`${this.getUrl({ version: 'stable' })}/etf/sector-weightings?symbol=${symbol}&apikey=${this.apiKey}`,
`${this.getUrl({ version: 'stable' })}/etf/sector-weightings?${queryParams.toString()}`,
{
signal: AbortSignal.timeout(requestTimeout)
}
@ -242,12 +269,17 @@ export class FinancialModelingPrepService implements DataProviderInterface {
}
try {
const queryParams = new URLSearchParams({
symbol,
apikey: this.apiKey
});
const response: {
[date: string]: DataProviderHistoricalResponse;
} = {};
const dividends = await fetch(
`${this.getUrl({ version: 'stable' })}/dividends?symbol=${symbol}&apikey=${this.apiKey}`,
`${this.getUrl({ version: 'stable' })}/dividends?${queryParams.toString()}`,
{
signal: AbortSignal.timeout(requestTimeout)
}
@ -307,8 +339,15 @@ export class FinancialModelingPrepService implements DataProviderInterface {
? addYears(currentFrom, MAX_YEARS_PER_REQUEST)
: to;
const queryParams = new URLSearchParams({
symbol,
apikey: this.apiKey,
from: format(currentFrom, DATE_FORMAT),
to: format(currentTo, DATE_FORMAT)
});
const historical = await fetch(
`${this.getUrl({ version: 'stable' })}/historical-price-eod/full?symbol=${symbol}&apikey=${this.apiKey}&from=${format(currentFrom, DATE_FORMAT)}&to=${format(currentTo, DATE_FORMAT)}`,
`${this.getUrl({ version: 'stable' })}/historical-price-eod/full?${queryParams.toString()}`,
{
signal: AbortSignal.timeout(requestTimeout)
}
@ -363,6 +402,11 @@ export class FinancialModelingPrepService implements DataProviderInterface {
[symbol: string]: Pick<SymbolProfile, 'currency'>;
} = {};
const queryParams = new URLSearchParams({
symbols: symbols.join(','),
apikey: this.apiKey
});
const [assetProfileResolutions, quotes] = await Promise.all([
this.prismaService.assetProfileResolution.findMany({
where: {
@ -371,7 +415,7 @@ export class FinancialModelingPrepService implements DataProviderInterface {
}
}),
fetch(
`${this.getUrl({ version: 'stable' })}/batch-quote-short?symbols=${symbols.join(',')}&apikey=${this.apiKey}`,
`${this.getUrl({ version: 'stable' })}/batch-quote-short?${queryParams.toString()}`,
{
signal: AbortSignal.timeout(requestTimeout)
}
@ -463,12 +507,18 @@ export class FinancialModelingPrepService implements DataProviderInterface {
const assetProfileBySymbolMap: {
[symbol: string]: Partial<SymbolProfile>;
} = {};
let items: LookupItem[] = [];
try {
if (isISIN(query?.toUpperCase())) {
const queryParams = new URLSearchParams({
apikey: this.apiKey,
isin: query.toUpperCase()
});
const result = await fetch(
`${this.getUrl({ version: 'stable' })}/search-isin?isin=${query.toUpperCase()}&apikey=${this.apiKey}`,
`${this.getUrl({ version: 'stable' })}/search-isin?${queryParams.toString()}`,
{
signal: AbortSignal.timeout(requestTimeout)
}
@ -494,8 +544,13 @@ export class FinancialModelingPrepService implements DataProviderInterface {
};
});
} else {
const queryParams = new URLSearchParams({
query,
apikey: this.apiKey
});
const result = await fetch(
`${this.getUrl({ version: 'stable' })}/search-symbol?query=${query}&apikey=${this.apiKey}`,
`${this.getUrl({ version: 'stable' })}/search-symbol?${queryParams.toString()}`,
{
signal: AbortSignal.timeout(
this.configurationService.get('REQUEST_TIMEOUT')

34
apps/api/src/services/data-provider/ghostfolio/ghostfolio.service.ts

@ -116,11 +116,14 @@ export class GhostfolioService implements DataProviderInterface {
} = {};
try {
const queryParams = new URLSearchParams({
granularity,
from: format(from, DATE_FORMAT),
to: format(to, DATE_FORMAT)
});
const response = await fetch(
`${this.URL}/v2/data-providers/ghostfolio/dividends/${symbol}?from=${format(from, DATE_FORMAT)}&granularity=${granularity}&to=${format(
to,
DATE_FORMAT
)}`,
`${this.URL}/v2/data-providers/ghostfolio/dividends/${symbol}?${queryParams.toString()}`,
{
headers: await this.getRequestHeaders(),
signal: AbortSignal.timeout(requestTimeout)
@ -165,11 +168,14 @@ export class GhostfolioService implements DataProviderInterface {
[symbol: string]: { [date: string]: DataProviderHistoricalResponse };
}> {
try {
const queryParams = new URLSearchParams({
granularity,
from: format(from, DATE_FORMAT),
to: format(to, DATE_FORMAT)
});
const response = await fetch(
`${this.URL}/v2/data-providers/ghostfolio/historical/${symbol}?from=${format(from, DATE_FORMAT)}&granularity=${granularity}&to=${format(
to,
DATE_FORMAT
)}`,
`${this.URL}/v2/data-providers/ghostfolio/historical/${symbol}?${queryParams.toString()}`,
{
headers: await this.getRequestHeaders(),
signal: AbortSignal.timeout(requestTimeout)
@ -235,8 +241,12 @@ export class GhostfolioService implements DataProviderInterface {
}
try {
const queryParams = new URLSearchParams({
symbols: symbols.join(',')
});
const response = await fetch(
`${this.URL}/v2/data-providers/ghostfolio/quotes?symbols=${symbols.join(',')}`,
`${this.URL}/v2/data-providers/ghostfolio/quotes?${queryParams.toString()}`,
{
headers: await this.getRequestHeaders(),
signal: AbortSignal.timeout(requestTimeout)
@ -288,8 +298,12 @@ export class GhostfolioService implements DataProviderInterface {
let searchResult: LookupResponse = { items: [] };
try {
const queryParams = new URLSearchParams({
query
});
const response = await fetch(
`${this.URL}/v2/data-providers/ghostfolio/lookup?query=${query}`,
`${this.URL}/v2/data-providers/ghostfolio/lookup?${queryParams.toString()}`,
{
headers: await this.getRequestHeaders(),
signal: AbortSignal.timeout(requestTimeout)

2
apps/client/src/app/components/access-table/access-table.component.html

@ -73,7 +73,7 @@
<button mat-menu-item (click)="onUpdateAccess(element.id)">
<span class="align-items-center d-flex">
<ion-icon class="mr-2" name="create-outline" />
<span i18n>Edit</span>
<span><ng-container i18n>Edit</ng-container>...</span>
</span>
</button>
}

6
apps/client/src/app/components/admin-jobs/admin-jobs.html

@ -205,14 +205,16 @@
</button>
<mat-menu #jobActionsMenu="matMenu" xPosition="before">
<button mat-menu-item (click)="onViewData(element.data)">
<ng-container i18n>View Data</ng-container>
<span><ng-container i18n>View Data</ng-container>...</span>
</button>
<button
mat-menu-item
[disabled]="element.stacktrace?.length <= 0"
(click)="onViewStacktrace(element.stacktrace)"
>
<ng-container i18n>View Stacktrace</ng-container>
<span
><ng-container i18n>View Stacktrace</ng-container>...</span
>
</button>
<button mat-menu-item (click)="onExecuteJob(element.id)">
<ng-container i18n>Execute Job</ng-container>

2
apps/client/src/app/components/admin-market-data/admin-market-data.html

@ -264,7 +264,7 @@
>
<span class="align-items-center d-flex">
<ion-icon class="mr-2" name="create-outline" />
<span i18n>Edit</span>
<span><ng-container i18n>Edit</ng-container>...</span>
</span>
</a>
<hr class="m-0" />

2
apps/client/src/app/components/admin-platform/admin-platform.component.html

@ -71,7 +71,7 @@
<button mat-menu-item (click)="onUpdatePlatform(element)">
<span class="align-items-center d-flex">
<ion-icon class="mr-2" name="create-outline" />
<span i18n>Edit</span>
<span><ng-container i18n>Edit</ng-container>...</span>
</span>
</button>
<hr class="m-0" />

2
apps/client/src/app/components/admin-tag/admin-tag.component.html

@ -64,7 +64,7 @@
<button mat-menu-item (click)="onUpdateTag(element)">
<span class="align-items-center d-flex">
<ion-icon class="mr-2" name="create-outline" />
<span i18n>Edit</span>
<span><ng-container i18n>Edit</ng-container>...</span>
</span>
</button>
<hr class="m-0" />

4
apps/client/src/app/components/admin-users/admin-users.html

@ -222,7 +222,9 @@
>
<span class="align-items-center d-flex">
<ion-icon class="mr-2" name="person-outline" />
<span i18n>View Details</span>
<span
><ng-container i18n>View Details</ng-container>...</span
>
</span>
</button>
@if (hasPermissionToImpersonateAllUsers) {

6
libs/ui/src/lib/accounts-table/accounts-table.component.html

@ -7,7 +7,7 @@
(click)="onTransferBalance()"
>
<ion-icon class="mr-2" name="arrow-redo-outline" />
<ng-container i18n>Transfer Cash Balance</ng-container>...
<span><ng-container i18n>Transfer Cash Balance</ng-container>...</span>
</button>
</div>
}
@ -304,13 +304,13 @@
<button mat-menu-item (click)="onOpenAccountDetailDialog(element.id)">
<span class="align-items-center d-flex">
<ion-icon class="mr-2" name="wallet-outline" />
<span i18n>View Details</span>
<span><ng-container i18n>View Details</ng-container>...</span>
</span>
</button>
<button mat-menu-item (click)="onUpdateAccount(element)">
<span class="align-items-center d-flex">
<ion-icon class="mr-2" name="create-outline" />
<span i18n>Edit</span>
<span><ng-container i18n>Edit</ng-container>...</span>
</span>
</button>
<hr class="m-0" />

18
libs/ui/src/lib/activities-table/activities-table.component.html

@ -6,7 +6,7 @@
(click)="onImport()"
>
<ion-icon class="mr-2" name="cloud-upload-outline" />
<ng-container i18n>Import Activities</ng-container>...
<span><ng-container i18n>Import Activities</ng-container>...</span>
</button>
@if (hasPermissionToExportActivities) {
<button
@ -26,7 +26,7 @@
>
<span class="align-items-center d-flex">
<ion-icon class="mr-2" name="color-wand-outline" />
<ng-container i18n>Import Dividends</ng-container>...
<span><ng-container i18n>Import Dividends</ng-container>...</span>
</span>
</button>
@if (hasPermissionToExportActivities) {
@ -379,7 +379,9 @@
>
<span class="align-items-center d-flex">
<ion-icon class="mr-2" name="cloud-upload-outline" />
<ng-container i18n>Import Activities</ng-container>...
<span
><ng-container i18n>Import Activities</ng-container>...</span
>
</span>
</button>
}
@ -391,7 +393,9 @@
>
<span class="align-items-center d-flex">
<ion-icon class="mr-2" name="color-wand-outline" />
<ng-container i18n>Import Dividends</ng-container>...
<span
><ng-container i18n>Import Dividends</ng-container>...</span
>
</span>
</button>
}
@ -443,20 +447,20 @@
<button mat-menu-item (click)="onClickActivity(element)">
<span class="align-items-center d-flex">
<ion-icon class="mr-2" name="tablet-landscape-outline" />
<span i18n>View Holding</span>
<span><ng-container i18n>View Holding</ng-container>...</span>
</span>
</button>
}
<button mat-menu-item (click)="onUpdateActivity(element)">
<span class="align-items-center d-flex">
<ion-icon class="mr-2" name="create-outline" />
<span i18n>Edit</span>
<span><ng-container i18n>Edit</ng-container>...</span>
</span>
</button>
<button mat-menu-item (click)="onCloneActivity(element)">
<span class="align-items-center d-flex">
<ion-icon class="mr-2" name="copy-outline" />
<span i18n>Clone</span>
<span><ng-container i18n>Clone</ng-container>...</span>
</span>
</button>
<button

46
libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.stories.ts

@ -22,7 +22,7 @@ export default {
type Story = StoryObj<GfPortfolioProportionChartComponent>;
export const Simple: Story = {
export const Default: Story = {
args: {
baseCurrency: 'USD',
data: {
@ -37,3 +37,47 @@ export const Simple: Story = {
locale: 'en-US'
}
};
export const InPercentage: Story = {
args: {
data: {
US: { name: 'United States', value: 0.6515000000000001 },
NL: { name: 'Netherlands', value: 0.006 },
DE: { name: 'Germany', value: 0.0031 },
GB: { name: 'United Kingdom', value: 0.0124 },
CA: { name: 'Canada', value: 0.0247 },
IE: { name: 'Ireland', value: 0.0112 },
SE: { name: 'Sweden', value: 0.0016 },
ES: { name: 'Spain', value: 0.0042 },
AU: { name: 'Australia', value: 0.0022 },
FR: { name: 'France', value: 0.0012 },
UY: { name: 'Uruguay', value: 0.0012 },
CH: { name: 'Switzerland', value: 0.004099999999999999 },
LU: { name: 'Luxembourg', value: 0.0012 },
BR: { name: 'Brazil', value: 0.0006 },
HK: { name: 'Hong Kong', value: 0.0006 },
IT: { name: 'Italy', value: 0.0005 },
CN: { name: 'China', value: 0.002 },
KR: { name: 'South Korea', value: 0.0006 },
BM: { name: 'Bermuda', value: 0.0011 },
ZA: { name: 'South Africa', value: 0.0004 },
SG: { name: 'Singapore', value: 0.0003 },
IL: { name: 'Israel', value: 0.001 },
DK: { name: 'Denmark', value: 0.0002 },
PE: { name: 'Peru', value: 0.0002 },
NO: { name: 'Norway', value: 0.0002 },
KY: { name: 'Cayman Islands', value: 0.0001 },
IN: { name: 'India', value: 0.0001 },
TW: { name: 'Taiwan', value: 0.0002 },
GR: { name: 'Greece', value: 0.0001 },
CL: { name: 'Chile', value: 0.0001 },
MX: { name: 'Mexico', value: 0 },
RU: { name: 'Russia', value: 0 },
IS: { name: 'Iceland', value: 0 },
JP: { name: 'Japan', value: 0 },
BE: { name: 'Belgium', value: 0 }
},
isInPercent: true,
keys: ['name']
}
};

20
package-lock.json

@ -1,12 +1,12 @@
{
"name": "ghostfolio",
"version": "2.219.0",
"version": "2.220.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "ghostfolio",
"version": "2.219.0",
"version": "2.220.0",
"hasInstallScript": true,
"license": "AGPL-3.0",
"dependencies": {
@ -23,7 +23,7 @@
"@angular/service-worker": "20.2.4",
"@codewithdan/observable-store": "2.2.15",
"@date-fns/utc": "2.1.0",
"@internationalized/number": "3.6.3",
"@internationalized/number": "3.6.5",
"@ionic/angular": "8.7.8",
"@keyv/redis": "4.4.0",
"@nestjs/bull": "11.0.4",
@ -140,7 +140,7 @@
"jest-environment-jsdom": "29.7.0",
"jest-preset-angular": "14.6.0",
"nx": "21.5.1",
"prettier": "3.6.2",
"prettier": "3.7.3",
"prettier-plugin-organize-attributes": "1.0.0",
"prisma": "6.19.0",
"react": "18.2.0",
@ -6031,9 +6031,9 @@
}
},
"node_modules/@internationalized/number": {
"version": "3.6.3",
"resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.6.3.tgz",
"integrity": "sha512-p+Zh1sb6EfrfVaS86jlHGQ9HA66fJhV9x5LiE5vCbZtXEHAuhcmUZUdZ4WrFpUBfNalr2OkAJI5AcKEQF+Lebw==",
"version": "3.6.5",
"resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.6.5.tgz",
"integrity": "sha512-6hY4Kl4HPBvtfS62asS/R22JzNNy8vi/Ssev7x6EobfCp+9QIB2hKvI2EtbdJ0VSQacxVNtqhE/NmF/NZ0gm6g==",
"license": "Apache-2.0",
"dependencies": {
"@swc/helpers": "^0.5.0"
@ -35749,9 +35749,9 @@
}
},
"node_modules/prettier": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
"version": "3.7.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.3.tgz",
"integrity": "sha512-QgODejq9K3OzoBbuyobZlUhznP5SKwPqp+6Q6xw6o8gnhr4O85L2U915iM2IDcfF2NPXVaM9zlo9tdwipnYwzg==",
"dev": true,
"license": "MIT",
"bin": {

6
package.json

@ -1,6 +1,6 @@
{
"name": "ghostfolio",
"version": "2.219.0",
"version": "2.220.0",
"homepage": "https://ghostfol.io",
"license": "AGPL-3.0",
"repository": "https://github.com/ghostfolio/ghostfolio",
@ -67,7 +67,7 @@
"@angular/service-worker": "20.2.4",
"@codewithdan/observable-store": "2.2.15",
"@date-fns/utc": "2.1.0",
"@internationalized/number": "3.6.3",
"@internationalized/number": "3.6.5",
"@ionic/angular": "8.7.8",
"@keyv/redis": "4.4.0",
"@nestjs/bull": "11.0.4",
@ -184,7 +184,7 @@
"jest-environment-jsdom": "29.7.0",
"jest-preset-angular": "14.6.0",
"nx": "21.5.1",
"prettier": "3.6.2",
"prettier": "3.7.3",
"prettier-plugin-organize-attributes": "1.0.0",
"prisma": "6.19.0",
"react": "18.2.0",

Loading…
Cancel
Save