From 6a195a7a361a0466808f145ee351bf6d76c2cc0b Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 28 Oct 2024 20:03:10 +0100 Subject: [PATCH 01/65] Feature/extend sitemap.xml by resources sub pages in de (#3993) * Extend sitemap.xml --- apps/api/src/assets/sitemap.xml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/apps/api/src/assets/sitemap.xml b/apps/api/src/assets/sitemap.xml index 17a6bc0f..3a0f44ff 100644 --- a/apps/api/src/assets/sitemap.xml +++ b/apps/api/src/assets/sitemap.xml @@ -56,10 +56,22 @@ https://ghostfol.io/de/ressourcen ${currentDate}T00:00:00+00:00 + + https://ghostfol.io/de/ressourcen/lexikon + ${currentDate}T00:00:00+00:00 + + + https://ghostfol.io/de/ressourcen/maerkte + ${currentDate}T00:00:00+00:00 + https://ghostfol.io/de/ressourcen/personal-finance-tools ${currentDate}T00:00:00+00:00 + + https://ghostfol.io/de/ressourcen/ratgeber + ${currentDate}T00:00:00+00:00 + https://ghostfol.io/de/ueber-uns ${currentDate}T00:00:00+00:00 From 5de176a7ef829bc441bd7998638bb50981cd677e Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 28 Oct 2024 20:03:37 +0100 Subject: [PATCH 02/65] Feature/rename allocation cluster risk x ray rule (#3994) * Rename Allocation Cluster Risk to Economic Market Cluster Risk * Update changelog --- CHANGELOG.md | 9 +++++---- apps/api/src/app/portfolio/portfolio.service.ts | 10 +++++----- apps/api/src/app/user/user.service.ts | 12 ++++++------ .../developed-markets.ts | 4 ++-- .../emerging-markets.ts | 4 ++-- .../app/pages/portfolio/fire/fire-page.component.ts | 10 +++++----- .../src/app/pages/portfolio/fire/fire-page.html | 4 ++-- .../lib/interfaces/x-ray-rules-settings.interface.ts | 4 ++-- 8 files changed, 29 insertions(+), 28 deletions(-) rename apps/api/src/models/rules/{allocation-cluster-risk => economic-market-cluster-risk}/developed-markets.ts (95%) rename apps/api/src/models/rules/{allocation-cluster-risk => economic-market-cluster-risk}/emerging-markets.ts (95%) diff --git a/CHANGELOG.md b/CHANGELOG.md index f419ecfa..0dcbcc74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Restructured the resources page +- Renamed the static portfolio analysis rule from _Allocation Cluster Risk_ to _Economic Market Cluster Risk_ (Developed Markets and Emerging Markets) - Improved the language localization for German (`de`) - Switched the `consistent-generic-constructors` rule from `warn` to `error` in the `eslint` configuration - Switched the `consistent-type-assertions` rule from `warn` to `error` in the `eslint` configuration @@ -29,15 +30,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed an issue with the X-axis scale of the dividend timeline on the analysis page - Fixed an issue with the X-axis scale of the investment timeline on the analysis page - Fixed an issue with the X-axis scale of the portfolio evolution chart on the analysis page -- Fixed an issue in the calculation of the static portfolio analysis rule: Allocation Cluster Risk (Developed Markets) -- Fixed an issue in the calculation of the static portfolio analysis rule: Allocation Cluster Risk (Emerging Markets) +- Fixed an issue in the calculation of the static portfolio analysis rule: _Allocation Cluster Risk_ (Developed Markets) +- Fixed an issue in the calculation of the static portfolio analysis rule: _Allocation Cluster Risk_ (Emerging Markets) ## 2.118.0 - 2024-10-23 ### Added -- Added a new static portfolio analysis rule: Allocation Cluster Risk (Developed Markets) -- Added a new static portfolio analysis rule: Allocation Cluster Risk (Emerging Markets) +- Added a new static portfolio analysis rule: _Allocation Cluster Risk_ (Developed Markets) +- Added a new static portfolio analysis rule: _Allocation Cluster Risk_ (Emerging Markets) - Added support for mutual funds in the _EOD Historical Data_ service ### Changed diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index d88d925a..d4018686 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -7,10 +7,10 @@ import { UserService } from '@ghostfolio/api/app/user/user.service'; import { getFactor } from '@ghostfolio/api/helper/portfolio.helper'; import { AccountClusterRiskCurrentInvestment } from '@ghostfolio/api/models/rules/account-cluster-risk/current-investment'; import { AccountClusterRiskSingleAccount } from '@ghostfolio/api/models/rules/account-cluster-risk/single-account'; -import { AllocationClusterRiskDevelopedMarkets } from '@ghostfolio/api/models/rules/allocation-cluster-risk/developed-markets'; -import { AllocationClusterRiskEmergingMarkets } from '@ghostfolio/api/models/rules/allocation-cluster-risk/emerging-markets'; import { CurrencyClusterRiskBaseCurrencyCurrentInvestment } from '@ghostfolio/api/models/rules/currency-cluster-risk/base-currency-current-investment'; import { CurrencyClusterRiskCurrentInvestment } from '@ghostfolio/api/models/rules/currency-cluster-risk/current-investment'; +import { EconomicMarketClusterRiskDevelopedMarkets } from '@ghostfolio/api/models/rules/economic-market-cluster-risk/developed-markets'; +import { EconomicMarketClusterRiskEmergingMarkets } from '@ghostfolio/api/models/rules/economic-market-cluster-risk/emerging-markets'; import { EmergencyFundSetup } from '@ghostfolio/api/models/rules/emergency-fund/emergency-fund-setup'; import { FeeRatioInitialInvestment } from '@ghostfolio/api/models/rules/fees/fee-ratio-initial-investment'; import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service'; @@ -1193,16 +1193,16 @@ export class PortfolioService { userSettings ) : undefined, - allocationClusterRisk: + economicMarketClusterRisk: summary.ordersCount > 0 ? await this.rulesService.evaluate( [ - new AllocationClusterRiskDevelopedMarkets( + new EconomicMarketClusterRiskDevelopedMarkets( this.exchangeRateDataService, marketsTotalInBaseCurrency, markets.developedMarkets.valueInBaseCurrency ), - new AllocationClusterRiskEmergingMarkets( + new EconomicMarketClusterRiskEmergingMarkets( this.exchangeRateDataService, marketsTotalInBaseCurrency, markets.emergingMarkets.valueInBaseCurrency diff --git a/apps/api/src/app/user/user.service.ts b/apps/api/src/app/user/user.service.ts index 556d2834..288e2aba 100644 --- a/apps/api/src/app/user/user.service.ts +++ b/apps/api/src/app/user/user.service.ts @@ -4,10 +4,10 @@ import { environment } from '@ghostfolio/api/environments/environment'; import { PortfolioChangedEvent } from '@ghostfolio/api/events/portfolio-changed.event'; import { AccountClusterRiskCurrentInvestment } from '@ghostfolio/api/models/rules/account-cluster-risk/current-investment'; import { AccountClusterRiskSingleAccount } from '@ghostfolio/api/models/rules/account-cluster-risk/single-account'; -import { AllocationClusterRiskDevelopedMarkets } from '@ghostfolio/api/models/rules/allocation-cluster-risk/developed-markets'; -import { AllocationClusterRiskEmergingMarkets } from '@ghostfolio/api/models/rules/allocation-cluster-risk/emerging-markets'; import { CurrencyClusterRiskBaseCurrencyCurrentInvestment } from '@ghostfolio/api/models/rules/currency-cluster-risk/base-currency-current-investment'; import { CurrencyClusterRiskCurrentInvestment } from '@ghostfolio/api/models/rules/currency-cluster-risk/current-investment'; +import { EconomicMarketClusterRiskDevelopedMarkets } from '@ghostfolio/api/models/rules/economic-market-cluster-risk/developed-markets'; +import { EconomicMarketClusterRiskEmergingMarkets } from '@ghostfolio/api/models/rules/economic-market-cluster-risk/emerging-markets'; import { EmergencyFundSetup } from '@ghostfolio/api/models/rules/emergency-fund/emergency-fund-setup'; import { FeeRatioInitialInvestment } from '@ghostfolio/api/models/rules/fees/fee-ratio-initial-investment'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; @@ -217,14 +217,14 @@ export class UserService { undefined, {} ).getSettings(user.Settings.settings), - AllocationClusterRiskDevelopedMarkets: - new AllocationClusterRiskDevelopedMarkets( + EconomicMarketClusterRiskDevelopedMarkets: + new EconomicMarketClusterRiskDevelopedMarkets( undefined, undefined, undefined ).getSettings(user.Settings.settings), - AllocationClusterRiskEmergingMarkets: - new AllocationClusterRiskEmergingMarkets( + EconomicMarketClusterRiskEmergingMarkets: + new EconomicMarketClusterRiskEmergingMarkets( undefined, undefined, undefined diff --git a/apps/api/src/models/rules/allocation-cluster-risk/developed-markets.ts b/apps/api/src/models/rules/economic-market-cluster-risk/developed-markets.ts similarity index 95% rename from apps/api/src/models/rules/allocation-cluster-risk/developed-markets.ts rename to apps/api/src/models/rules/economic-market-cluster-risk/developed-markets.ts index 068ebdda..15e11392 100644 --- a/apps/api/src/models/rules/allocation-cluster-risk/developed-markets.ts +++ b/apps/api/src/models/rules/economic-market-cluster-risk/developed-markets.ts @@ -3,7 +3,7 @@ import { Rule } from '@ghostfolio/api/models/rule'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { UserSettings } from '@ghostfolio/common/interfaces'; -export class AllocationClusterRiskDevelopedMarkets extends Rule { +export class EconomicMarketClusterRiskDevelopedMarkets extends Rule { private currentValueInBaseCurrency: number; private developedMarketsValueInBaseCurrency: number; @@ -13,7 +13,7 @@ export class AllocationClusterRiskDevelopedMarkets extends Rule { developedMarketsValueInBaseCurrency: number ) { super(exchangeRateDataService, { - key: AllocationClusterRiskDevelopedMarkets.name, + key: EconomicMarketClusterRiskDevelopedMarkets.name, name: 'Developed Markets' }); diff --git a/apps/api/src/models/rules/allocation-cluster-risk/emerging-markets.ts b/apps/api/src/models/rules/economic-market-cluster-risk/emerging-markets.ts similarity index 95% rename from apps/api/src/models/rules/allocation-cluster-risk/emerging-markets.ts rename to apps/api/src/models/rules/economic-market-cluster-risk/emerging-markets.ts index e7c10751..8fccdf1d 100644 --- a/apps/api/src/models/rules/allocation-cluster-risk/emerging-markets.ts +++ b/apps/api/src/models/rules/economic-market-cluster-risk/emerging-markets.ts @@ -3,7 +3,7 @@ import { Rule } from '@ghostfolio/api/models/rule'; import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { UserSettings } from '@ghostfolio/common/interfaces'; -export class AllocationClusterRiskEmergingMarkets extends Rule { +export class EconomicMarketClusterRiskEmergingMarkets extends Rule { private currentValueInBaseCurrency: number; private emergingMarketsValueInBaseCurrency: number; @@ -13,7 +13,7 @@ export class AllocationClusterRiskEmergingMarkets extends Rule { emergingMarketsValueInBaseCurrency: number ) { super(exchangeRateDataService, { - key: AllocationClusterRiskEmergingMarkets.name, + key: EconomicMarketClusterRiskEmergingMarkets.name, name: 'Emerging Markets' }); diff --git a/apps/client/src/app/pages/portfolio/fire/fire-page.component.ts b/apps/client/src/app/pages/portfolio/fire/fire-page.component.ts index ea83500c..d20c6691 100644 --- a/apps/client/src/app/pages/portfolio/fire/fire-page.component.ts +++ b/apps/client/src/app/pages/portfolio/fire/fire-page.component.ts @@ -22,9 +22,9 @@ import { takeUntil } from 'rxjs/operators'; }) export class FirePageComponent implements OnDestroy, OnInit { public accountClusterRiskRules: PortfolioReportRule[]; - public allocationClusterRiskRules: PortfolioReportRule[]; public currencyClusterRiskRules: PortfolioReportRule[]; public deviceType: string; + public economicMarketClusterRiskRules: PortfolioReportRule[]; public emergencyFundRules: PortfolioReportRule[]; public feeRules: PortfolioReportRule[]; public fireWealth: Big; @@ -204,15 +204,15 @@ export class FirePageComponent implements OnDestroy, OnInit { } ) ?? null; - this.allocationClusterRiskRules = - portfolioReport.rules['allocationClusterRisk']?.filter( + this.currencyClusterRiskRules = + portfolioReport.rules['currencyClusterRisk']?.filter( ({ isActive }) => { return isActive; } ) ?? null; - this.currencyClusterRiskRules = - portfolioReport.rules['currencyClusterRisk']?.filter( + this.economicMarketClusterRiskRules = + portfolioReport.rules['economicMarketClusterRisk']?.filter( ({ isActive }) => { return isActive; } diff --git a/apps/client/src/app/pages/portfolio/fire/fire-page.html b/apps/client/src/app/pages/portfolio/fire/fire-page.html index 4eedca30..7a336b62 100644 --- a/apps/client/src/app/pages/portfolio/fire/fire-page.html +++ b/apps/client/src/app/pages/portfolio/fire/fire-page.html @@ -176,7 +176,7 @@

- Allocation Cluster Risks + Economic Market Cluster Risks @if (user?.subscription?.type === 'Basic') { } @@ -188,7 +188,7 @@ user?.settings?.isExperimentalFeatures " [isLoading]="isLoadingPortfolioReport" - [rules]="allocationClusterRiskRules" + [rules]="economicMarketClusterRiskRules" [settings]="user?.settings?.xRayRules" (rulesUpdated)="onRulesUpdated($event)" /> diff --git a/libs/common/src/lib/interfaces/x-ray-rules-settings.interface.ts b/libs/common/src/lib/interfaces/x-ray-rules-settings.interface.ts index f38a8e6a..579d589b 100644 --- a/libs/common/src/lib/interfaces/x-ray-rules-settings.interface.ts +++ b/libs/common/src/lib/interfaces/x-ray-rules-settings.interface.ts @@ -1,10 +1,10 @@ export interface XRayRulesSettings { AccountClusterRiskCurrentInvestment?: RuleSettings; AccountClusterRiskSingleAccount?: RuleSettings; - AllocationClusterRiskDevelopedMarkets?: RuleSettings; - AllocationClusterRiskEmergingMarkets?: RuleSettings; CurrencyClusterRiskBaseCurrencyCurrentInvestment?: RuleSettings; CurrencyClusterRiskCurrentInvestment?: RuleSettings; + EconomicMarketClusterRiskDevelopedMarkets?: RuleSettings; + EconomicMarketClusterRiskEmergingMarkets?: RuleSettings; EmergencyFundSetup?: RuleSettings; FeeRatioInitialInvestment?: RuleSettings; } From ab44773945e2702477fb9034acc5ab7d6d623b31 Mon Sep 17 00:00:00 2001 From: dw-0 Date: Tue, 29 Oct 2024 17:29:03 +0100 Subject: [PATCH 03/65] Feature/Switch consistent-indexed-object-style eslint rule from warn to off (#3999) * Switch consistent-indexed-object-style eslint rule from warn to off * Update changelog --------- Signed-off-by: Dominik Willner --- .eslintrc.json | 4 ++-- CHANGELOG.md | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 79d34dd0..75e36246 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -39,6 +39,7 @@ "plugin:@typescript-eslint/stylistic-type-checked" ], "rules": { + "@typescript-eslint/consistent-indexed-object-style": "off", "@typescript-eslint/dot-notation": "off", "@typescript-eslint/explicit-member-accessibility": [ "off", @@ -142,8 +143,7 @@ // The following rules are part of @typescript-eslint/stylistic-type-checked // and can be remove once solved - "@typescript-eslint/prefer-nullish-coalescing": "warn", // TODO: Requires strictNullChecks: true - "@typescript-eslint/consistent-indexed-object-style": "warn" + "@typescript-eslint/prefer-nullish-coalescing": "warn" // TODO: Requires strictNullChecks: true } } ], diff --git a/CHANGELOG.md b/CHANGELOG.md index 0dcbcc74..1a809350 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Renamed the static portfolio analysis rule from _Allocation Cluster Risk_ to _Economic Market Cluster Risk_ (Developed Markets and Emerging Markets) - Improved the language localization for German (`de`) - Switched the `consistent-generic-constructors` rule from `warn` to `error` in the `eslint` configuration +- Switched the `consistent-indexed-object-style` rule from `warn` to `off` in the `eslint` configuration - Switched the `consistent-type-assertions` rule from `warn` to `error` in the `eslint` configuration - Switched the `prefer-optional-chain` rule from `warn` to `error` in the `eslint` configuration From 53ae0d8aa7b1761a33a67f7a666301242ff5ef21 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Tue, 29 Oct 2024 18:47:54 +0100 Subject: [PATCH 04/65] Feature/upgrade nx to version 20.0.6 (#3996) * Upgrade Nx to version 20.0.6 * Update changelog --- CHANGELOG.md | 1 + package-lock.json | 684 +++++++++------------------------------------- package.json | 22 +- 3 files changed, 142 insertions(+), 565 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a809350..750e733d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Switched the `consistent-indexed-object-style` rule from `warn` to `off` in the `eslint` configuration - Switched the `consistent-type-assertions` rule from `warn` to `error` in the `eslint` configuration - Switched the `prefer-optional-chain` rule from `warn` to `error` in the `eslint` configuration +- Upgraded `Nx` from version `20.0.3` to `20.0.6` ## 2.119.0 - 2024-10-26 diff --git a/package-lock.json b/package-lock.json index 49260020..0761fdf9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ghostfolio", - "version": "2.118.0", + "version": "2.119.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ghostfolio", - "version": "2.118.0", + "version": "2.119.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { @@ -107,16 +107,16 @@ "@angular/pwa": "18.2.9", "@nestjs/schematics": "10.0.1", "@nestjs/testing": "10.1.3", - "@nx/angular": "20.0.3", - "@nx/cypress": "20.0.3", - "@nx/eslint-plugin": "20.0.3", - "@nx/jest": "20.0.3", - "@nx/js": "20.0.3", - "@nx/nest": "20.0.3", - "@nx/node": "20.0.3", - "@nx/storybook": "20.0.3", - "@nx/web": "20.0.3", - "@nx/workspace": "20.0.3", + "@nx/angular": "20.0.6", + "@nx/cypress": "20.0.6", + "@nx/eslint-plugin": "20.0.6", + "@nx/jest": "20.0.6", + "@nx/js": "20.0.6", + "@nx/nest": "20.0.6", + "@nx/node": "20.0.6", + "@nx/storybook": "20.0.6", + "@nx/web": "20.0.6", + "@nx/workspace": "20.0.6", "@schematics/angular": "18.2.9", "@simplewebauthn/types": "9.0.1", "@storybook/addon-essentials": "8.3.6", @@ -147,7 +147,7 @@ "jest": "29.7.0", "jest-environment-jsdom": "29.7.0", "jest-preset-angular": "14.1.0", - "nx": "20.0.3", + "nx": "20.0.6", "prettier": "3.3.3", "prettier-plugin-organize-attributes": "1.0.0", "prisma": "5.21.1", @@ -3688,63 +3688,6 @@ "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/@eslint/config-array": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz", - "integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@eslint/object-schema": "^2.1.4", - "debug": "^4.3.1", - "minimatch": "^3.1.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "optional": true, - "peer": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@eslint/core": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.7.0.tgz", - "integrity": "sha512-xp5Jirz5DyPYlPiKat8jaq0EmYvDXKKpzTbxXMpT9eqlRJkRKIz9AGMdlvYjih+im+QlhWrpvVjl8IPC/lHlUw==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "peer": true, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, "node_modules/@eslint/eslintrc": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", @@ -3869,67 +3812,12 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@eslint/object-schema": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", - "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "peer": true, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.1.tgz", - "integrity": "sha512-HFZ4Mp26nbWk9d/BpvP0YNL6W4UoZF0VFcTw/aPPA8RpOxeFQgK+ClABGgAUXs9Y/RGX/l1vOmrqz1MQt9MNuw==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "levn": "^0.4.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, "node_modules/@hexagon/base64": { "version": "1.1.28", "resolved": "https://registry.npmjs.org/@hexagon/base64/-/base64-1.1.28.tgz", "integrity": "sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw==", "license": "MIT" }, - "node_modules/@humanfs/core": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.0.tgz", - "integrity": "sha512-2cbWIHbZVEweE853g8jymffCA+NCMiuqeECeBBLm8dg2oFdjuGJhgN4UAbI+6v0CKbbhvtXA4qV8YR5Ji86nmw==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "peer": true, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.5", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.5.tgz", - "integrity": "sha512-KSPA4umqSG4LHYRodq31VDwKAvaTF4xmVlzM8Aeh4PlU1JQ3IG0wiA8C25d3RQ9nJyM3mBHyI53K06VVL/oFFg==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "@humanfs/core": "^0.19.0", - "@humanwhocodes/retry": "^0.3.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", @@ -3992,22 +3880,6 @@ "dev": true, "license": "BSD-3-Clause" }, - "node_modules/@humanwhocodes/retry": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", - "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "peer": true, - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, "node_modules/@inquirer/checkbox": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-2.5.0.tgz", @@ -6557,19 +6429,19 @@ } }, "node_modules/@nx/angular": { - "version": "20.0.3", - "resolved": "https://registry.npmjs.org/@nx/angular/-/angular-20.0.3.tgz", - "integrity": "sha512-d9xekjP9onlRzW0Vz1My4USkiOuihZU3nM/SgGj7i2ZL7W/Fu81H7CAxlmsH93/XPresHaAnZSCSx0ofq5YyCA==", + "version": "20.0.6", + "resolved": "https://registry.npmjs.org/@nx/angular/-/angular-20.0.6.tgz", + "integrity": "sha512-0UfCEp4JeQEYMpUjaipHEH/V/GRHGCd+vgPN9EdhpkSqw2YyuBXlZiX1q0DgzMxZRRBRTB+p37FgRPu32lOI6g==", "dev": true, "license": "MIT", "dependencies": { "@module-federation/enhanced": "0.6.6", - "@nx/devkit": "20.0.3", - "@nx/eslint": "20.0.3", - "@nx/js": "20.0.3", - "@nx/web": "20.0.3", - "@nx/webpack": "20.0.3", - "@nx/workspace": "20.0.3", + "@nx/devkit": "20.0.6", + "@nx/eslint": "20.0.6", + "@nx/js": "20.0.6", + "@nx/web": "20.0.6", + "@nx/webpack": "20.0.6", + "@nx/workspace": "20.0.6", "@phenomnomnominal/tsquery": "~5.0.1", "@typescript-eslint/type-utils": "^8.0.0", "chalk": "^4.1.0", @@ -6662,15 +6534,15 @@ } }, "node_modules/@nx/cypress": { - "version": "20.0.3", - "resolved": "https://registry.npmjs.org/@nx/cypress/-/cypress-20.0.3.tgz", - "integrity": "sha512-Bnm3Soa3aEIzmbJMtjmgGko5FVvBWh2I0stV+eMmLC8y9mCOQ3W4i+pXjpD6zVLAfZBF80W9e1ON7kdlfpM2Rw==", + "version": "20.0.6", + "resolved": "https://registry.npmjs.org/@nx/cypress/-/cypress-20.0.6.tgz", + "integrity": "sha512-b26Ucgf+dAdTRlBGhFi8Xjeqw1mbUrxn3nwAOYNwuivc+CZCeokba5/orldNAlBlJKvHe0QmSAI3wpDjdU05Ww==", "dev": true, "license": "MIT", "dependencies": { - "@nx/devkit": "20.0.3", - "@nx/eslint": "20.0.3", - "@nx/js": "20.0.3", + "@nx/devkit": "20.0.6", + "@nx/eslint": "20.0.6", + "@nx/js": "20.0.6", "@phenomnomnominal/tsquery": "~5.0.1", "detect-port": "^1.5.1", "tslib": "^2.3.0" @@ -6685,9 +6557,9 @@ } }, "node_modules/@nx/devkit": { - "version": "20.0.3", - "resolved": "https://registry.npmjs.org/@nx/devkit/-/devkit-20.0.3.tgz", - "integrity": "sha512-tB6iQ2opvipyy+4J0eImW/Nl8SoILPpDodwnThDJ2U2mflHG6/+3Wl6Q1hXieOnjT+ZE++ve91aYDEAi9OMwvA==", + "version": "20.0.6", + "resolved": "https://registry.npmjs.org/@nx/devkit/-/devkit-20.0.6.tgz", + "integrity": "sha512-vUjVVEJgfq/roCzDDZDXduwnhVXl1MM5No2UELUka2oNBK09pPigdFxzUNh8XvmOyFskCGDTLKH/dAO5yTD5Bg==", "dev": true, "license": "MIT", "dependencies": { @@ -6705,14 +6577,14 @@ } }, "node_modules/@nx/eslint": { - "version": "20.0.3", - "resolved": "https://registry.npmjs.org/@nx/eslint/-/eslint-20.0.3.tgz", - "integrity": "sha512-uWS1jvGj5T2GOMRit8HqC0LOo1BxEzQejxEioIfLVaoO8bd67FdZQh2Tz3Qon9V05VXm8pEHQv/1NVNqanzgBQ==", + "version": "20.0.6", + "resolved": "https://registry.npmjs.org/@nx/eslint/-/eslint-20.0.6.tgz", + "integrity": "sha512-07Ign5GQXZif6zHDR2oB4wkf2amSvoGhYWJ17fmqDsMF/nWYOohL+DbjAaqDORXWXL1bnmRBaj/lAkDNsmW3QA==", "dev": true, "license": "MIT", "dependencies": { - "@nx/devkit": "20.0.3", - "@nx/js": "20.0.3", + "@nx/devkit": "20.0.6", + "@nx/js": "20.0.6", "semver": "^7.5.3", "tslib": "^2.3.0", "typescript": "~5.4.2" @@ -6728,15 +6600,14 @@ } }, "node_modules/@nx/eslint-plugin": { - "version": "20.0.3", - "resolved": "https://registry.npmjs.org/@nx/eslint-plugin/-/eslint-plugin-20.0.3.tgz", - "integrity": "sha512-KQi2rHwRQjQDqt7g4666LdKVBUNcHubX1MlXCB/f0ejCJunlybqK4aA+LiM0KIQpieevvIlAHJuTdZQ2M7q2HQ==", + "version": "20.0.6", + "resolved": "https://registry.npmjs.org/@nx/eslint-plugin/-/eslint-plugin-20.0.6.tgz", + "integrity": "sha512-wFWg9X4dhRVY5pIAuqXLKTQSL3FzWHbV5kpg7S+y2X3jFg3pezqa8EDBkAcerSk7rour1G2hlXAfHX/W3HCrBQ==", "dev": true, "license": "MIT", "dependencies": { - "@eslint/compat": "^1.1.1", - "@nx/devkit": "20.0.3", - "@nx/js": "20.0.3", + "@nx/devkit": "20.0.6", + "@nx/js": "20.0.6", "@typescript-eslint/type-utils": "^8.0.0", "@typescript-eslint/utils": "^8.0.0", "chalk": "^4.1.0", @@ -6756,105 +6627,6 @@ } } }, - "node_modules/@nx/eslint-plugin/node_modules/@eslint/compat": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.2.1.tgz", - "integrity": "sha512-JbHG2TWuCeNzh87fXo+/46Z1LEo9DBA9T188d0fZgGxAD+cNyS6sx9fdiyxjGPBMyQVRlCutTByZ6a5+YMkF7g==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "peerDependencies": { - "eslint": "^9.10.0" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } - } - }, - "node_modules/@nx/eslint-plugin/node_modules/@eslint/eslintrc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", - "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@nx/eslint-plugin/node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@nx/eslint-plugin/node_modules/@eslint/js": { - "version": "9.13.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.13.0.tgz", - "integrity": "sha512-IFLyoY4d72Z5y/6o/BazFBezupzI/taV8sGumxTAVw3lXG9A6md1Dc34T9s1FoD/an9pJH8RHbAxsaEbBed9lA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@nx/eslint-plugin/node_modules/@types/estree": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", - "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, - "node_modules/@nx/eslint-plugin/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "node_modules/@nx/eslint-plugin/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -6871,19 +6643,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@nx/eslint-plugin/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "node_modules/@nx/eslint-plugin/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -6901,150 +6660,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@nx/eslint-plugin/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@nx/eslint-plugin/node_modules/eslint": { - "version": "9.13.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.13.0.tgz", - "integrity": "sha512-EYZK6SX6zjFHST/HRytOdA/zE72Cq/bfw45LSyuwrdvcclb/gqV8RRQxywOBEWO2+WDpva6UZa4CcDeJKzUCFA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.11.0", - "@eslint/config-array": "^0.18.0", - "@eslint/core": "^0.7.0", - "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "9.13.0", - "@eslint/plugin-kit": "^0.2.0", - "@humanfs/node": "^0.16.5", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.3.1", - "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.1.0", - "eslint-visitor-keys": "^4.1.0", - "espree": "^10.2.0", - "esquery": "^1.5.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } - } - }, - "node_modules/@nx/eslint-plugin/node_modules/eslint-visitor-keys": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz", - "integrity": "sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "peer": true, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@nx/eslint-plugin/node_modules/espree": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.2.0.tgz", - "integrity": "sha512-upbkBJbckcCNBDBDXEbuhjbP68n+scUd3k/U2EkyM9nw+I/jPiL4cLF/Al06CF96wRltFda16sxDFrxsI1v0/g==", - "dev": true, - "license": "BSD-2-Clause", - "optional": true, - "peer": true, - "dependencies": { - "acorn": "^8.12.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.1.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@nx/eslint-plugin/node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "flat-cache": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@nx/eslint-plugin/node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - }, - "engines": { - "node": ">=16" - } - }, "node_modules/@nx/eslint-plugin/node_modules/globals": { "version": "15.11.0", "resolved": "https://registry.npmjs.org/globals/-/globals-15.11.0.tgz", @@ -7068,45 +6683,6 @@ "node": ">=8" } }, - "node_modules/@nx/eslint-plugin/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@nx/eslint-plugin/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, - "node_modules/@nx/eslint-plugin/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "optional": true, - "peer": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/@nx/eslint-plugin/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -7135,16 +6711,16 @@ } }, "node_modules/@nx/jest": { - "version": "20.0.3", - "resolved": "https://registry.npmjs.org/@nx/jest/-/jest-20.0.3.tgz", - "integrity": "sha512-ZC9OPSh1htpYEh+kGZAew5r1pLtOCZo3odqW7/DalCti2XOTVit8yuw1DahIqrzZ3BzcTq+q9W9Ng17mMVCaCA==", + "version": "20.0.6", + "resolved": "https://registry.npmjs.org/@nx/jest/-/jest-20.0.6.tgz", + "integrity": "sha512-/v9NavOOWcUpzgbjfYip0zipneJPhKUQd5rU3bTr0CqCJw0I+YQXotToUkzzMQYT6zmNrq7ySTMH1N8rXdy7NQ==", "dev": true, "license": "MIT", "dependencies": { "@jest/reporters": "^29.4.1", "@jest/test-result": "^29.4.1", - "@nx/devkit": "20.0.3", - "@nx/js": "20.0.3", + "@nx/devkit": "20.0.6", + "@nx/js": "20.0.6", "@phenomnomnominal/tsquery": "~5.0.1", "chalk": "^4.1.0", "identity-obj-proxy": "3.0.0", @@ -7215,9 +6791,9 @@ } }, "node_modules/@nx/js": { - "version": "20.0.3", - "resolved": "https://registry.npmjs.org/@nx/js/-/js-20.0.3.tgz", - "integrity": "sha512-UbltxJyfEXL586kk7yxOTNHtigd7rq7atmcOmMphcxbeWk9HzeowVh6j6OA4MAKwYauomjCqsJbvWURI8qf+pg==", + "version": "20.0.6", + "resolved": "https://registry.npmjs.org/@nx/js/-/js-20.0.6.tgz", + "integrity": "sha512-/bAMtcgKX1Te3yCzbbv+QQLnFwb6SxE0iCc6EzxiLepmGhnd0iOArUqepB1mVipfeaO37n00suFjFv1xsaqLHg==", "dev": true, "license": "MIT", "dependencies": { @@ -7228,8 +6804,8 @@ "@babel/preset-env": "^7.23.2", "@babel/preset-typescript": "^7.22.5", "@babel/runtime": "^7.22.6", - "@nx/devkit": "20.0.3", - "@nx/workspace": "20.0.3", + "@nx/devkit": "20.0.6", + "@nx/workspace": "20.0.6", "@zkochan/js-yaml": "0.0.7", "babel-plugin-const-enum": "^1.0.1", "babel-plugin-macros": "^2.8.0", @@ -7503,17 +7079,17 @@ } }, "node_modules/@nx/nest": { - "version": "20.0.3", - "resolved": "https://registry.npmjs.org/@nx/nest/-/nest-20.0.3.tgz", - "integrity": "sha512-8Il3BcyiJfj5vAXszlX+QqRlvAU9eudoc59q8GIXzU9vyrjrYcazpxkQr+qJ2q8mLdvJYJYMpA4ilbE6y6PQPw==", + "version": "20.0.6", + "resolved": "https://registry.npmjs.org/@nx/nest/-/nest-20.0.6.tgz", + "integrity": "sha512-vACNZ+jTURJfVIpLQURgbR12O7mOqoVjCSfqbXBIC9pc4kYqShPW0SnAybwxKR+zpWGeO1nel7VXEH95HgAXuA==", "dev": true, "license": "MIT", "dependencies": { "@nestjs/schematics": "^9.1.0", - "@nx/devkit": "20.0.3", - "@nx/eslint": "20.0.3", - "@nx/js": "20.0.3", - "@nx/node": "20.0.3", + "@nx/devkit": "20.0.6", + "@nx/eslint": "20.0.6", + "@nx/js": "20.0.6", + "@nx/node": "20.0.6", "tslib": "^2.3.0" } }, @@ -7645,23 +7221,23 @@ } }, "node_modules/@nx/node": { - "version": "20.0.3", - "resolved": "https://registry.npmjs.org/@nx/node/-/node-20.0.3.tgz", - "integrity": "sha512-50JqKEVRmh2g9bxBxB0hDVzNae6rf9d5Iu8bTxpF55h6kivdoiYF793/awpxCpE6XPCij9IafeoaT77Ug8dQYA==", + "version": "20.0.6", + "resolved": "https://registry.npmjs.org/@nx/node/-/node-20.0.6.tgz", + "integrity": "sha512-/6khofVKgpdglkSE6XDz9tk4kCeEXQaIPOH1PgWqY25hoim/VSXjZ1XMVmPvnvd7m2lsFLDrqZlwIGWTrT2cFw==", "dev": true, "license": "MIT", "dependencies": { - "@nx/devkit": "20.0.3", - "@nx/eslint": "20.0.3", - "@nx/jest": "20.0.3", - "@nx/js": "20.0.3", + "@nx/devkit": "20.0.6", + "@nx/eslint": "20.0.6", + "@nx/jest": "20.0.6", + "@nx/js": "20.0.6", "tslib": "^2.3.0" } }, "node_modules/@nx/nx-darwin-arm64": { - "version": "20.0.3", - "resolved": "https://registry.npmjs.org/@nx/nx-darwin-arm64/-/nx-darwin-arm64-20.0.3.tgz", - "integrity": "sha512-/wjxSuQZOHwDopNAfuh2BTsaDtDECjTDrKHJdTknrSVjdsB2b1hwSdL7Ct0PXBiSnf+0gfYBR2fuPmLZYb3AXA==", + "version": "20.0.6", + "resolved": "https://registry.npmjs.org/@nx/nx-darwin-arm64/-/nx-darwin-arm64-20.0.6.tgz", + "integrity": "sha512-SUVfEqzl/iy2NzTbpY2E9lHSxs8c9QERhTILp5OOt0Vgmhn9iTxVEIoSCjzz/MyX066eARarUymUyK4JCg3mqw==", "cpu": [ "arm64" ], @@ -7676,9 +7252,9 @@ } }, "node_modules/@nx/nx-darwin-x64": { - "version": "20.0.3", - "resolved": "https://registry.npmjs.org/@nx/nx-darwin-x64/-/nx-darwin-x64-20.0.3.tgz", - "integrity": "sha512-Gobgkvsx61P5TI0uuDQTI/D2AXJt3xnBuAWQ4V/NW/OpkvL8j/q8zk81uK0tumVvIc4p5kSlGmQ46/ytSrdqvg==", + "version": "20.0.6", + "resolved": "https://registry.npmjs.org/@nx/nx-darwin-x64/-/nx-darwin-x64-20.0.6.tgz", + "integrity": "sha512-JI0kcJGBeIj3sb+kC0nZMOSXFnvCOtGbAVK3HHJ9DSRxckLq5bImwqdfYSNJL9ocU8YU+Qds/SercEV02gQOkQ==", "cpu": [ "x64" ], @@ -7693,9 +7269,9 @@ } }, "node_modules/@nx/nx-freebsd-x64": { - "version": "20.0.3", - "resolved": "https://registry.npmjs.org/@nx/nx-freebsd-x64/-/nx-freebsd-x64-20.0.3.tgz", - "integrity": "sha512-nbYp89BP0z0DzuaUH/yVVhCbL96vUUaKmCVmmdlvQRgiaX89BChAMuEdLNSeaDHFrhgTYB87ku3Ok6DRCAIOcg==", + "version": "20.0.6", + "resolved": "https://registry.npmjs.org/@nx/nx-freebsd-x64/-/nx-freebsd-x64-20.0.6.tgz", + "integrity": "sha512-om9Sh5Pg5aRDlBWyHMAX/1swLSj2pCqk1grXN6RcJ8O3tXLI35fj4wz6sPDRASwC1xuHwET2DG/20Ec6n1Ko3A==", "cpu": [ "x64" ], @@ -7710,9 +7286,9 @@ } }, "node_modules/@nx/nx-linux-arm-gnueabihf": { - "version": "20.0.3", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-20.0.3.tgz", - "integrity": "sha512-eKIYJPvXO/N1FjteZHC4DLV0u+2h70RmrDQODPztfl3mI5AjCwFdLf9RPN1D+SuNdfK1WwZIszY+FiVxrpK19A==", + "version": "20.0.6", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-20.0.6.tgz", + "integrity": "sha512-XIomXUqnH3w1aqRu0T+Wcn9roXT1bG1PjuX+bmGLkSiZ+ZyY/zYfhg6WKbox3TqQcdC1jNUkzEQlLGcfWaGc6w==", "cpu": [ "arm" ], @@ -7727,9 +7303,9 @@ } }, "node_modules/@nx/nx-linux-arm64-gnu": { - "version": "20.0.3", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-20.0.3.tgz", - "integrity": "sha512-CDFy2WNsMZvxshtGdFV/yCux1XkLtcqh0FiitNvGdgNugXXp3CLVEUx6dI3VBuIBNGbfozdr7n+fuXN6F2S4MQ==", + "version": "20.0.6", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-20.0.6.tgz", + "integrity": "sha512-Asx2F+WtauELssmrQf1y4ZeiMIsgbL/+PnD+WgbvHVWbl7cRUfLJqEhOR5fQG6CiNTIXvOyzXMoaJVA9hTub+Q==", "cpu": [ "arm64" ], @@ -7744,9 +7320,9 @@ } }, "node_modules/@nx/nx-linux-arm64-musl": { - "version": "20.0.3", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-20.0.3.tgz", - "integrity": "sha512-BGrSRNPuDyj0yeP2MyzF1MMij1KO4Q/2YSgBbYzVSc8JdrUqf+3rqI8VXNTr3FcAKMTPgFjkFZ3XD3s/62gsdg==", + "version": "20.0.6", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-20.0.6.tgz", + "integrity": "sha512-4lyBaLWSv7VNMOXWxtuDNiSOE4M5QGiVHimSvQ9PBwgnrvEuc6fCv/Nc8ecU0rINHRQJruYMTD/kKBCsahwJUQ==", "cpu": [ "arm64" ], @@ -7761,9 +7337,9 @@ } }, "node_modules/@nx/nx-linux-x64-gnu": { - "version": "20.0.3", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-20.0.3.tgz", - "integrity": "sha512-xGGjQ8q5XuF0/432APvAi/OSMdR3LZ1yQ9hYh+JGvM5wh44I3UbgBXRCJlsHp+t2hdlilF6kpaeMSiP1Z9CEbg==", + "version": "20.0.6", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-20.0.6.tgz", + "integrity": "sha512-HGZzX7un/rJvADKwN27HM0e3Gx19hSndCoqZUtqHgrFRdUvTfHTWNpT6uZ5XW/5bNnRKdUinY9DHhlYpE0u4KQ==", "cpu": [ "x64" ], @@ -7778,9 +7354,9 @@ } }, "node_modules/@nx/nx-linux-x64-musl": { - "version": "20.0.3", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-20.0.3.tgz", - "integrity": "sha512-fTmZNbq3QQF5BLGPB8PGuFuNo3s2F86IQDOUYWpjXiuKjoI1Y5yM14RQpHLwYOGnUNoKYOhlv/JAyFrDX6ALZA==", + "version": "20.0.6", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-20.0.6.tgz", + "integrity": "sha512-OwMq+ozzCOCtAViOouHbe/MXqep/q4EKg44YelUqVNIe/2XimcIfMlBQFk1DOcmibesxa3yWMKAdg2IGUnG+pQ==", "cpu": [ "x64" ], @@ -7795,9 +7371,9 @@ } }, "node_modules/@nx/nx-win32-arm64-msvc": { - "version": "20.0.3", - "resolved": "https://registry.npmjs.org/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-20.0.3.tgz", - "integrity": "sha512-hdtfg9pIzhtLqqGvsTemQYwe+kqqL1JGNgrlf3V59HSbbAADYZbHnliujoRybJo7dpeS/DDTNMNeblg99tFQLA==", + "version": "20.0.6", + "resolved": "https://registry.npmjs.org/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-20.0.6.tgz", + "integrity": "sha512-2D8TIjyi5dJLy4cx8u7YKunW6+EG9FAuBUo75qMCozTBw1EPTK2lzwLE2d8C7WOxBA148O2wzD5uiX1vCt2Tzg==", "cpu": [ "arm64" ], @@ -7812,9 +7388,9 @@ } }, "node_modules/@nx/nx-win32-x64-msvc": { - "version": "20.0.3", - "resolved": "https://registry.npmjs.org/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-20.0.3.tgz", - "integrity": "sha512-HcqE8AlWuwcsIOj0OnKDQ3q7L0RZsOrBRhDRKbJeUnIFz/t2R3q8Y6trrqTyFAafgW6JNLBp+tgcUyfHPUy/eQ==", + "version": "20.0.6", + "resolved": "https://registry.npmjs.org/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-20.0.6.tgz", + "integrity": "sha512-B83kpN1+KdJ97P0Rw/KRyZ5fZPtKimvwg/TAJdWR1D8oqdrpaZwgTd9dcsTNavvynUsPqM3GdjmFKzTYTZ4MFQ==", "cpu": [ "x64" ], @@ -7829,30 +7405,30 @@ } }, "node_modules/@nx/storybook": { - "version": "20.0.3", - "resolved": "https://registry.npmjs.org/@nx/storybook/-/storybook-20.0.3.tgz", - "integrity": "sha512-Nt5iWUYDEvA+S8I9aFnF5fhODV+H74XR+lS8kza+o0K4kRqhdvzp7WB3SanEuY/k3kbhP7M99dbBRZXJi3Sgdg==", + "version": "20.0.6", + "resolved": "https://registry.npmjs.org/@nx/storybook/-/storybook-20.0.6.tgz", + "integrity": "sha512-eqQKs67bRb9vutCt+dcR5CUhnSiQ2X82cYNryHEu/u8qE0LRfmCxxWh1DUNGxz1v1SYquo6RBo0qORm8oef3Pg==", "dev": true, "license": "MIT", "dependencies": { - "@nx/cypress": "20.0.3", - "@nx/devkit": "20.0.3", - "@nx/eslint": "20.0.3", - "@nx/js": "20.0.3", + "@nx/cypress": "20.0.6", + "@nx/devkit": "20.0.6", + "@nx/eslint": "20.0.6", + "@nx/js": "20.0.6", "@phenomnomnominal/tsquery": "~5.0.1", "semver": "^7.5.3", "tslib": "^2.3.0" } }, "node_modules/@nx/web": { - "version": "20.0.3", - "resolved": "https://registry.npmjs.org/@nx/web/-/web-20.0.3.tgz", - "integrity": "sha512-b3KpUeA0cI9JIpRBYEk/4sIs9nCI6RcXCmxFoyW60vYsr2VtPZjtLKbo3bBT7HLOk3iwAYYWzCY1cu0Xzig3Lg==", + "version": "20.0.6", + "resolved": "https://registry.npmjs.org/@nx/web/-/web-20.0.6.tgz", + "integrity": "sha512-lYu9FddREZYbjbjS9YYnXu+uGQUB6MptNvPNSvYRRUcdq7c8Kh10P21YyK2Ox7FsEUeqly+XVvhlKNXeQF5anw==", "dev": true, "license": "MIT", "dependencies": { - "@nx/devkit": "20.0.3", - "@nx/js": "20.0.3", + "@nx/devkit": "20.0.6", + "@nx/js": "20.0.6", "detect-port": "^1.5.1", "http-server": "^14.1.0", "picocolors": "^1.1.0", @@ -7860,17 +7436,17 @@ } }, "node_modules/@nx/webpack": { - "version": "20.0.3", - "resolved": "https://registry.npmjs.org/@nx/webpack/-/webpack-20.0.3.tgz", - "integrity": "sha512-r9oBx1BV3zm6292TZnQd+6dwSx9Gixl5AgneJmAVQvT65moLfg2bL8t0G6sSodxYChcXVB7mJriXDAJjMbb48w==", + "version": "20.0.6", + "resolved": "https://registry.npmjs.org/@nx/webpack/-/webpack-20.0.6.tgz", + "integrity": "sha512-LvjkJ0yVXDCNgxxIKYLMtEJVVdvBVHcB9mgwPdBfl38STAf/HwTuB7XXTZVYu+m9iPusU1VpFpaUlbpQN79f8A==", "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.23.2", "@module-federation/enhanced": "^0.6.0", "@module-federation/sdk": "^0.6.0", - "@nx/devkit": "20.0.3", - "@nx/js": "20.0.3", + "@nx/devkit": "20.0.6", + "@nx/js": "20.0.6", "@phenomnomnominal/tsquery": "~5.0.1", "ajv": "^8.12.0", "autoprefixer": "^10.4.9", @@ -8526,16 +8102,16 @@ } }, "node_modules/@nx/workspace": { - "version": "20.0.3", - "resolved": "https://registry.npmjs.org/@nx/workspace/-/workspace-20.0.3.tgz", - "integrity": "sha512-ctStDr9UlXt63v9wC1qS9lqLABSDfcfCH/FtQ6ZF5RjWIkzZS672g29gkT83L9B87dfRJYCH8yGGbvMJzq0qRA==", + "version": "20.0.6", + "resolved": "https://registry.npmjs.org/@nx/workspace/-/workspace-20.0.6.tgz", + "integrity": "sha512-A7lle47I4JggbhXoUVvkuvULqF0Xejy4LpE0txz9OIM5a9HxW1aIHYYQFuROBuVlMFxAJusPeYFJCCvb+qBxKw==", "dev": true, "license": "MIT", "dependencies": { - "@nx/devkit": "20.0.3", + "@nx/devkit": "20.0.6", "chalk": "^4.1.0", "enquirer": "~2.3.6", - "nx": "20.0.3", + "nx": "20.0.6", "tslib": "^2.3.0", "yargs-parser": "21.1.1" } @@ -27766,9 +27342,9 @@ "license": "MIT" }, "node_modules/nx": { - "version": "20.0.3", - "resolved": "https://registry.npmjs.org/nx/-/nx-20.0.3.tgz", - "integrity": "sha512-6ZuZ09IdMIwbklKqEwUAHspuVMsDr7TIcCyeytmdDC1XbA+Tbb93wriyJyiI9EBQw4StrlJF9vSXAZsuDiOKeA==", + "version": "20.0.6", + "resolved": "https://registry.npmjs.org/nx/-/nx-20.0.6.tgz", + "integrity": "sha512-z8PMPEXxtADwxsNXamZdDbx65fcNcR4gTmX7N94GKmpZNrjwd3m7RcnoYgQp5vA8kFQkMR+320mtq5NkGJPZvg==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -27811,16 +27387,16 @@ "nx-cloud": "bin/nx-cloud.js" }, "optionalDependencies": { - "@nx/nx-darwin-arm64": "20.0.3", - "@nx/nx-darwin-x64": "20.0.3", - "@nx/nx-freebsd-x64": "20.0.3", - "@nx/nx-linux-arm-gnueabihf": "20.0.3", - "@nx/nx-linux-arm64-gnu": "20.0.3", - "@nx/nx-linux-arm64-musl": "20.0.3", - "@nx/nx-linux-x64-gnu": "20.0.3", - "@nx/nx-linux-x64-musl": "20.0.3", - "@nx/nx-win32-arm64-msvc": "20.0.3", - "@nx/nx-win32-x64-msvc": "20.0.3" + "@nx/nx-darwin-arm64": "20.0.6", + "@nx/nx-darwin-x64": "20.0.6", + "@nx/nx-freebsd-x64": "20.0.6", + "@nx/nx-linux-arm-gnueabihf": "20.0.6", + "@nx/nx-linux-arm64-gnu": "20.0.6", + "@nx/nx-linux-arm64-musl": "20.0.6", + "@nx/nx-linux-x64-gnu": "20.0.6", + "@nx/nx-linux-x64-musl": "20.0.6", + "@nx/nx-win32-arm64-msvc": "20.0.6", + "@nx/nx-win32-x64-msvc": "20.0.6" }, "peerDependencies": { "@swc-node/register": "^1.8.0", diff --git a/package.json b/package.json index b83ce5c5..75826156 100644 --- a/package.json +++ b/package.json @@ -153,16 +153,16 @@ "@angular/pwa": "18.2.9", "@nestjs/schematics": "10.0.1", "@nestjs/testing": "10.1.3", - "@nx/angular": "20.0.3", - "@nx/cypress": "20.0.3", - "@nx/eslint-plugin": "20.0.3", - "@nx/jest": "20.0.3", - "@nx/js": "20.0.3", - "@nx/nest": "20.0.3", - "@nx/node": "20.0.3", - "@nx/storybook": "20.0.3", - "@nx/web": "20.0.3", - "@nx/workspace": "20.0.3", + "@nx/angular": "20.0.6", + "@nx/cypress": "20.0.6", + "@nx/eslint-plugin": "20.0.6", + "@nx/jest": "20.0.6", + "@nx/js": "20.0.6", + "@nx/nest": "20.0.6", + "@nx/node": "20.0.6", + "@nx/storybook": "20.0.6", + "@nx/web": "20.0.6", + "@nx/workspace": "20.0.6", "@schematics/angular": "18.2.9", "@simplewebauthn/types": "9.0.1", "@storybook/addon-essentials": "8.3.6", @@ -193,7 +193,7 @@ "jest": "29.7.0", "jest-environment-jsdom": "29.7.0", "jest-preset-angular": "14.1.0", - "nx": "20.0.3", + "nx": "20.0.6", "prettier": "3.3.3", "prettier-plugin-organize-attributes": "1.0.0", "prisma": "5.21.1", From 10e725b51adc1104ec8eefc8c6308c86f55a639c Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 30 Oct 2024 21:39:20 +0100 Subject: [PATCH 05/65] Feature/Add dataProviderGhostfolioDailyRequests to Analytics (#4001) * Add dataProviderGhostfolioDailyRequests to Analytics --- apps/api/src/app/user/user.service.ts | 53 ++++++++++++------- apps/api/src/services/cron.service.ts | 13 ++++- .../migration.sql | 2 + prisma/schema.prisma | 11 ++-- 4 files changed, 54 insertions(+), 25 deletions(-) create mode 100644 prisma/migrations/20241029190323_added_data_provider_ghostfolio_daily_requests_to_analytics/migration.sql diff --git a/apps/api/src/app/user/user.service.ts b/apps/api/src/app/user/user.service.ts index 288e2aba..443a2a05 100644 --- a/apps/api/src/app/user/user.service.ts +++ b/apps/api/src/app/user/user.service.ts @@ -37,7 +37,7 @@ import { UserWithSettings } from '@ghostfolio/common/types'; import { Injectable } from '@nestjs/common'; import { EventEmitter2 } from '@nestjs/event-emitter'; import { Prisma, Role, User } from '@prisma/client'; -import { differenceInDays } from 'date-fns'; +import { differenceInDays, subDays } from 'date-fns'; import { sortBy, without } from 'lodash'; const crypto = require('crypto'); @@ -60,6 +60,13 @@ export class UserService { return this.prismaService.user.count(args); } + public createAccessToken(password: string, salt: string): string { + const hash = crypto.createHmac('sha512', salt); + hash.update(password); + + return hash.digest('hex'); + } + public async getUser( { Account, id, permissions, Settings, subscription }: UserWithSettings, aLocale = locale @@ -358,13 +365,6 @@ export class UserService { }); } - public createAccessToken(password: string, salt: string): string { - const hash = crypto.createHmac('sha512', salt); - hash.update(password); - - return hash.digest('hex'); - } - public async createUser({ data }: { @@ -426,17 +426,6 @@ export class UserService { return user; } - public async updateUser(params: { - where: Prisma.UserWhereUniqueInput; - data: Prisma.UserUpdateInput; - }): Promise { - const { where, data } = params; - return this.prismaService.user.update({ - data, - where - }); - } - public async deleteUser(where: Prisma.UserWhereUniqueInput): Promise { try { await this.prismaService.access.deleteMany({ @@ -473,6 +462,32 @@ export class UserService { }); } + public async resetAnalytics() { + return this.prismaService.analytics.updateMany({ + data: { + dataProviderGhostfolioDailyRequests: 0 + }, + where: { + updatedAt: { + gte: subDays(new Date(), 1) + } + } + }); + } + + public async updateUser({ + data, + where + }: { + data: Prisma.UserUpdateInput; + where: Prisma.UserWhereUniqueInput; + }): Promise { + return this.prismaService.user.update({ + data, + where + }); + } + public async updateUserSetting({ emitPortfolioChangedEvent, userId, diff --git a/apps/api/src/services/cron.service.ts b/apps/api/src/services/cron.service.ts index 17e970c1..7a1b30b5 100644 --- a/apps/api/src/services/cron.service.ts +++ b/apps/api/src/services/cron.service.ts @@ -1,3 +1,4 @@ +import { UserService } from '@ghostfolio/api/app/user/user.service'; import { DATA_GATHERING_QUEUE_PRIORITY_LOW, GATHER_ASSET_PROFILE_PROCESS, @@ -9,6 +10,7 @@ import { getAssetProfileIdentifier } from '@ghostfolio/common/helper'; import { Injectable } from '@nestjs/common'; import { Cron, CronExpression } from '@nestjs/schedule'; +import { ConfigurationService } from './configuration/configuration.service'; import { ExchangeRateDataService } from './exchange-rate-data/exchange-rate-data.service'; import { PropertyService } from './property/property.service'; import { DataGatheringService } from './queues/data-gathering/data-gathering.service'; @@ -19,10 +21,12 @@ export class CronService { private static readonly EVERY_SUNDAY_AT_LUNCH_TIME = '0 12 * * 0'; public constructor( + private readonly configurationService: ConfigurationService, private readonly dataGatheringService: DataGatheringService, private readonly exchangeRateDataService: ExchangeRateDataService, private readonly propertyService: PropertyService, - private readonly twitterBotService: TwitterBotService + private readonly twitterBotService: TwitterBotService, + private readonly userService: UserService ) {} @Cron(CronExpression.EVERY_HOUR) @@ -42,6 +46,13 @@ export class CronService { this.twitterBotService.tweetFearAndGreedIndex(); } + @Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT) + public async runEveryDayAtMidnight() { + if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) { + this.userService.resetAnalytics(); + } + } + @Cron(CronService.EVERY_SUNDAY_AT_LUNCH_TIME) public async runEverySundayAtTwelvePm() { if (await this.isDataGatheringEnabled()) { diff --git a/prisma/migrations/20241029190323_added_data_provider_ghostfolio_daily_requests_to_analytics/migration.sql b/prisma/migrations/20241029190323_added_data_provider_ghostfolio_daily_requests_to_analytics/migration.sql new file mode 100644 index 00000000..a9566dd8 --- /dev/null +++ b/prisma/migrations/20241029190323_added_data_provider_ghostfolio_daily_requests_to_analytics/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Analytics" ADD COLUMN "dataProviderGhostfolioDailyRequests" INTEGER NOT NULL DEFAULT 0; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 9fa55076..7f386a71 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -65,11 +65,12 @@ model AccountBalance { } model Analytics { - activityCount Int @default(0) - country String? - updatedAt DateTime @updatedAt - userId String @id - User User @relation(fields: [userId], onDelete: Cascade, references: [id]) + activityCount Int @default(0) + country String? + dataProviderGhostfolioDailyRequests Int @default(0) + updatedAt DateTime @updatedAt + userId String @id + User User @relation(fields: [userId], onDelete: Cascade, references: [id]) @@index([updatedAt]) } From db53ef54c4ba0c7cf25ceb4ce145bc791339bb6d Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 30 Oct 2024 21:39:51 +0100 Subject: [PATCH 06/65] Feature/use log levels to conditionally log prisma query events (#4003) * Use LOG_LEVELS for prisma query events * Update changelog --- CHANGELOG.md | 4 ++++ apps/api/src/services/prisma/prisma.module.ts | 5 ++-- .../api/src/services/prisma/prisma.service.ts | 24 ++++++++++++++++++- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 750e733d..44db5ad5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- Added support for log levels (`LOG_LEVELS`) to conditionally log `prisma` query events (`debug` or `verbose`) + ### Changed - Restructured the resources page diff --git a/apps/api/src/services/prisma/prisma.module.ts b/apps/api/src/services/prisma/prisma.module.ts index 3875c8ca..24da6104 100644 --- a/apps/api/src/services/prisma/prisma.module.ts +++ b/apps/api/src/services/prisma/prisma.module.ts @@ -1,9 +1,10 @@ import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; import { Module } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; @Module({ - providers: [PrismaService], - exports: [PrismaService] + exports: [PrismaService], + providers: [ConfigService, PrismaService] }) export class PrismaModule {} diff --git a/apps/api/src/services/prisma/prisma.service.ts b/apps/api/src/services/prisma/prisma.service.ts index e99d6ecf..4673cbd1 100644 --- a/apps/api/src/services/prisma/prisma.service.ts +++ b/apps/api/src/services/prisma/prisma.service.ts @@ -1,16 +1,38 @@ import { Injectable, Logger, + LogLevel, OnModuleDestroy, OnModuleInit } from '@nestjs/common'; -import { PrismaClient } from '@prisma/client'; +import { ConfigService } from '@nestjs/config'; +import { Prisma, PrismaClient } from '@prisma/client'; @Injectable() export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy { + public constructor(configService: ConfigService) { + let customLogLevels: LogLevel[]; + + try { + customLogLevels = JSON.parse( + configService.get('LOG_LEVELS') + ) as LogLevel[]; + } catch {} + + const log: Prisma.LogDefinition[] = + customLogLevels?.includes('debug') || customLogLevels?.includes('verbose') + ? [{ emit: 'stdout', level: 'query' }] + : []; + + super({ + log, + errorFormat: 'colorless' + }); + } + public async onModuleInit() { try { await this.$connect(); From d7f69020c21be3f611e89ed9dd0755b07929a7b3 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 30 Oct 2024 21:41:47 +0100 Subject: [PATCH 07/65] Release 2.120.0 (#4004) --- CHANGELOG.md | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44db5ad5..6feab65b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ 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.120.0 - 2024-10-30 ### Added diff --git a/package.json b/package.json index 75826156..134d72f9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ghostfolio", - "version": "2.119.0", + "version": "2.120.0", "homepage": "https://ghostfol.io", "license": "AGPL-3.0", "repository": "https://github.com/ghostfolio/ghostfolio", From 1ee9cd3de14e68c8ac459a8d1e6b3376ba93dfbb Mon Sep 17 00:00:00 2001 From: dw-0 Date: Thu, 31 Oct 2024 13:37:56 +0100 Subject: [PATCH 08/65] Feature/set stack and container names in docker-compose files (#4000) * Set stack and container names in docker-compose files * Update changelog --------- Signed-off-by: Dominik Willner --- CHANGELOG.md | 6 ++++++ docker/docker-compose.build.yml | 4 ++++ docker/docker-compose.dev.yml | 5 +++-- docker/docker-compose.yml | 4 ++++ 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6feab65b..0ed7f9af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ 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 + +### Added + +- Set the stack and container names in the `docker-compose` files (`docker-compose.yml`, `docker-compose.build.yml` and `docker-compose.dev.yml`) + ## 2.120.0 - 2024-10-30 ### Added diff --git a/docker/docker-compose.build.yml b/docker/docker-compose.build.yml index 02442c0c..9cc8eb1d 100644 --- a/docker/docker-compose.build.yml +++ b/docker/docker-compose.build.yml @@ -1,6 +1,8 @@ +name: ghostfolio_build services: ghostfolio: build: ../ + container_name: gf-application-build init: true env_file: - ../.env @@ -23,6 +25,7 @@ services: postgres: image: docker.io/library/postgres:15 + container_name: gf-postgres-build env_file: - ../.env healthcheck: @@ -35,6 +38,7 @@ services: redis: image: docker.io/library/redis:alpine + container_name: gf-redis-build env_file: - ../.env command: ['redis-server', '--requirepass', $REDIS_PASSWORD] diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml index 91f8f2c0..39a1d56e 100644 --- a/docker/docker-compose.dev.yml +++ b/docker/docker-compose.dev.yml @@ -1,7 +1,8 @@ +name: ghostfolio_dev services: postgres: image: docker.io/library/postgres:15 - container_name: postgres + container_name: gf-postgres-dev restart: unless-stopped env_file: - ../.env @@ -12,7 +13,7 @@ services: redis: image: docker.io/library/redis:alpine - container_name: redis + container_name: gf-redis-dev restart: unless-stopped env_file: - ../.env diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 642912b7..b8215c97 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -1,6 +1,8 @@ +name: ghostfolio services: ghostfolio: image: docker.io/ghostfolio/ghostfolio:latest + container_name: gf-application init: true cap_drop: - ALL @@ -27,6 +29,7 @@ services: postgres: image: docker.io/library/postgres:15 + container_name: gf-postgres cap_drop: - ALL cap_add: @@ -49,6 +52,7 @@ services: redis: image: docker.io/library/redis:alpine + container_name: gf-redis user: '999:1000' cap_drop: - ALL From 45c0487ba75702ce9e140f6b59568e11b5b4cc5f Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Thu, 31 Oct 2024 17:34:18 +0100 Subject: [PATCH 09/65] Bugfix/Set stack and container names in docker-compose files (#4005) * Set stack and container names in docker-compose files --- docker/docker-compose.build.yml | 2 +- docker/docker-compose.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/docker-compose.build.yml b/docker/docker-compose.build.yml index 9cc8eb1d..96829ad3 100644 --- a/docker/docker-compose.build.yml +++ b/docker/docker-compose.build.yml @@ -2,7 +2,7 @@ name: ghostfolio_build services: ghostfolio: build: ../ - container_name: gf-application-build + container_name: ghostfolio-build init: true env_file: - ../.env diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index b8215c97..c6ec5b3d 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -2,7 +2,7 @@ name: ghostfolio services: ghostfolio: image: docker.io/ghostfolio/ghostfolio:latest - container_name: gf-application + container_name: ghostfolio init: true cap_drop: - ALL From 0004c5dabead28ac545e81ed731a4df97fd549df Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 2 Nov 2024 13:36:33 +0100 Subject: [PATCH 10/65] Feature/revert permissions on entrypoint.sh in dockerfile (#4007) * Revert permissions * Update changelog --- CHANGELOG.md | 4 ++++ Dockerfile | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ed7f9af..397cc998 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Set the stack and container names in the `docker-compose` files (`docker-compose.yml`, `docker-compose.build.yml` and `docker-compose.dev.yml`) +### Changed + +- Reverted the permissions (`chmod 0700`) on `entrypoint.sh` in the `Dockerfile` + ## 2.120.0 - 2024-10-30 ### Added diff --git a/Dockerfile b/Dockerfile index 0e5c0d27..e6c38f27 100644 --- a/Dockerfile +++ b/Dockerfile @@ -61,7 +61,6 @@ RUN apt-get update && apt-get install -y --no-install-suggests \ COPY --chown=node:node --from=builder /ghostfolio/dist/apps /ghostfolio/apps COPY --chown=node:node ./docker/entrypoint.sh /ghostfolio/entrypoint.sh -RUN chmod 0700 /ghostfolio/entrypoint.sh WORKDIR /ghostfolio/apps/api EXPOSE ${PORT:-3333} USER node From 5f56812125e354cc187e953d74f1c4a884a8127a Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 2 Nov 2024 13:44:57 +0100 Subject: [PATCH 11/65] Feature/extend promotion system (#4008) * Extend promotion system --- apps/api/src/app/info/info.service.ts | 14 ++--- .../app/subscription/subscription.service.ts | 57 +++++++++++++++++-- apps/client/src/app/app.component.html | 1 + apps/client/src/app/app.component.ts | 13 +++++ .../components/header/header.component.html | 27 +++++++-- .../app/components/header/header.component.ts | 1 + .../user-account-membership.component.ts | 19 +++++-- .../user-account-membership.html | 10 ++++ .../pages/pricing/pricing-page.component.ts | 24 +++++--- .../src/app/pages/pricing/pricing-page.html | 18 ++++++ .../product-page.component.ts | 4 +- libs/common/src/lib/interfaces/index.ts | 4 +- .../src/lib/interfaces/info-item.interface.ts | 6 +- .../subscription-offer.interface.ts | 9 +++ .../lib/interfaces/subscription.interface.ts | 6 -- .../src/lib/interfaces/user.interface.ts | 4 +- libs/common/src/lib/types/index.ts | 4 +- ...type.ts => subscription-offer-key.type.ts} | 2 +- .../src/lib/types/user-with-settings.type.ts | 4 +- 19 files changed, 177 insertions(+), 50 deletions(-) create mode 100644 libs/common/src/lib/interfaces/subscription-offer.interface.ts delete mode 100644 libs/common/src/lib/interfaces/subscription.interface.ts rename libs/common/src/lib/types/{subscription-offer.type.ts => subscription-offer-key.type.ts} (71%) diff --git a/apps/api/src/app/info/info.service.ts b/apps/api/src/app/info/info.service.ts index bd291c51..f81ddd71 100644 --- a/apps/api/src/app/info/info.service.ts +++ b/apps/api/src/app/info/info.service.ts @@ -23,10 +23,10 @@ import { import { InfoItem, Statistics, - Subscription + SubscriptionOffer } from '@ghostfolio/common/interfaces'; import { permissions } from '@ghostfolio/common/permissions'; -import { SubscriptionOffer } from '@ghostfolio/common/types'; +import { SubscriptionOfferKey } from '@ghostfolio/common/types'; import { Injectable, Logger } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; @@ -101,7 +101,7 @@ export class InfoService { isUserSignupEnabled, platforms, statistics, - subscriptions + subscriptionOffers ] = await Promise.all([ this.benchmarkService.getBenchmarkAssetProfiles(), this.getDemoAuthToken(), @@ -110,7 +110,7 @@ export class InfoService { orderBy: { name: 'asc' } }), this.getStatistics(), - this.getSubscriptions() + this.getSubscriptionOffers() ]); if (isUserSignupEnabled) { @@ -125,7 +125,7 @@ export class InfoService { isReadOnlyMode, platforms, statistics, - subscriptions, + subscriptionOffers, baseCurrency: DEFAULT_CURRENCY, currencies: this.exchangeRateDataService.getCurrencies() }; @@ -314,8 +314,8 @@ export class InfoService { return statistics; } - private async getSubscriptions(): Promise<{ - [offer in SubscriptionOffer]: Subscription; + private async getSubscriptionOffers(): Promise<{ + [offer in SubscriptionOfferKey]: SubscriptionOffer; }> { if (!this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) { return undefined; diff --git a/apps/api/src/app/subscription/subscription.service.ts b/apps/api/src/app/subscription/subscription.service.ts index 7c1df023..47e6db00 100644 --- a/apps/api/src/app/subscription/subscription.service.ts +++ b/apps/api/src/app/subscription/subscription.service.ts @@ -1,8 +1,16 @@ import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; -import { DEFAULT_LANGUAGE_CODE } from '@ghostfolio/common/config'; +import { PropertyService } from '@ghostfolio/api/services/property/property.service'; +import { + DEFAULT_LANGUAGE_CODE, + PROPERTY_STRIPE_CONFIG +} from '@ghostfolio/common/config'; import { parseDate } from '@ghostfolio/common/helper'; -import { SubscriptionOffer, UserWithSettings } from '@ghostfolio/common/types'; +import { SubscriptionOffer } from '@ghostfolio/common/interfaces'; +import { + SubscriptionOfferKey, + UserWithSettings +} from '@ghostfolio/common/types'; import { SubscriptionType } from '@ghostfolio/common/types/subscription-type.type'; import { Injectable, Logger } from '@nestjs/common'; @@ -17,7 +25,8 @@ export class SubscriptionService { public constructor( private readonly configurationService: ConfigurationService, - private readonly prismaService: PrismaService + private readonly prismaService: PrismaService, + private readonly propertyService: PropertyService ) { this.stripe = new Stripe( this.configurationService.get('STRIPE_SECRET_KEY'), @@ -36,6 +45,18 @@ export class SubscriptionService { priceId: string; user: UserWithSettings; }) { + const subscriptionOffers: { + [offer in SubscriptionOfferKey]: SubscriptionOffer; + } = + ((await this.propertyService.getByKey(PROPERTY_STRIPE_CONFIG)) as any) ?? + {}; + + const subscriptionOffer = Object.values(subscriptionOffers).find( + (subscriptionOffer) => { + return subscriptionOffer.priceId === priceId; + } + ); + const checkoutSessionCreateParams: Stripe.Checkout.SessionCreateParams = { cancel_url: `${this.configurationService.get('ROOT_URL')}/${ user.Settings?.settings?.language ?? DEFAULT_LANGUAGE_CODE @@ -47,6 +68,13 @@ export class SubscriptionService { quantity: 1 } ], + locale: + (user.Settings?.settings + ?.language as Stripe.Checkout.SessionCreateParams.Locale) ?? + DEFAULT_LANGUAGE_CODE, + metadata: subscriptionOffer + ? { subscriptionOffer: JSON.stringify(subscriptionOffer) } + : {}, mode: 'payment', payment_method_types: ['card'], success_url: `${this.configurationService.get( @@ -73,17 +101,25 @@ export class SubscriptionService { public async createSubscription({ duration = '1 year', + durationExtension, price, userId }: { duration?: StringValue; + durationExtension?: StringValue; price: number; userId: string; }) { + let expiresAt = addMilliseconds(new Date(), ms(duration)); + + if (durationExtension) { + expiresAt = addMilliseconds(expiresAt, ms(durationExtension)); + } + await this.prismaService.subscription.create({ data: { + expiresAt, price, - expiresAt: addMilliseconds(new Date(), ms(duration)), User: { connect: { id: userId @@ -95,10 +131,21 @@ export class SubscriptionService { public async createSubscriptionViaStripe(aCheckoutSessionId: string) { try { + let durationExtension: StringValue; + const session = await this.stripe.checkout.sessions.retrieve(aCheckoutSessionId); + const subscriptionOffer: SubscriptionOffer = JSON.parse( + session.metadata.subscriptionOffer ?? '{}' + ); + + if (subscriptionOffer) { + durationExtension = subscriptionOffer.durationExtension; + } + await this.createSubscription({ + durationExtension, price: session.amount_total / 100, userId: session.client_reference_id }); @@ -121,7 +168,7 @@ export class SubscriptionService { return new Date(a.expiresAt) > new Date(b.expiresAt) ? a : b; }); - let offer: SubscriptionOffer = price ? 'renewal' : 'default'; + let offer: SubscriptionOfferKey = price ? 'renewal' : 'default'; if (isBefore(createdAt, parseDate('2023-01-01'))) { offer = 'renewal-early-bird-2023'; diff --git a/apps/client/src/app/app.component.html b/apps/client/src/app/app.component.html index b1285548..7560e15e 100644 --- a/apps/client/src/app/app.component.html +++ b/apps/client/src/app/app.component.html @@ -33,6 +33,7 @@ [deviceType]="deviceType" [hasPermissionToChangeDateRange]="hasPermissionToChangeDateRange" [hasPermissionToChangeFilters]="hasPermissionToChangeFilters" + [hasPromotion]="hasPromotion" [hasTabs]="hasTabs" [info]="info" [pageTitle]="pageTitle" diff --git a/apps/client/src/app/app.component.ts b/apps/client/src/app/app.component.ts index 75841686..86d4282a 100644 --- a/apps/client/src/app/app.component.ts +++ b/apps/client/src/app/app.component.ts @@ -57,6 +57,7 @@ export class AppComponent implements OnDestroy, OnInit { public hasPermissionToAccessFearAndGreedIndex: boolean; public hasPermissionToChangeDateRange: boolean; public hasPermissionToChangeFilters: boolean; + public hasPromotion = false; public hasTabs = false; public info: InfoItem; public pageTitle: string; @@ -136,6 +137,10 @@ export class AppComponent implements OnDestroy, OnInit { permissions.enableFearAndGreedIndex ); + this.hasPromotion = + !!this.info?.subscriptionOffers?.default?.coupon || + !!this.info?.subscriptionOffers?.default?.durationExtension; + this.impersonationStorageService .onChangeHasImpersonation() .pipe(takeUntil(this.unsubscribeSubject)) @@ -231,6 +236,14 @@ export class AppComponent implements OnDestroy, OnInit { this.hasInfoMessage = this.canCreateAccount || !!this.user?.systemMessage; + this.hasPromotion = + !!this.info?.subscriptionOffers?.[ + this.user?.subscription?.offer ?? 'default' + ]?.coupon || + !!this.info?.subscriptionOffers?.[ + this.user?.subscription?.offer ?? 'default' + ]?.durationExtension; + this.initializeTheme(this.user?.settings.colorScheme); this.changeDetectorRef.markForCheck(); diff --git a/apps/client/src/app/components/header/header.component.html b/apps/client/src/app/components/header/header.component.html index ff36c2eb..8a611d93 100644 --- a/apps/client/src/app/components/header/header.component.html +++ b/apps/client/src/app/components/header/header.component.html @@ -88,15 +88,20 @@
  • Pricing + + Pricing + @if (currentRoute !== routePricing && hasPromotion) { + % + } + +
  • }
  • @@ -290,12 +295,17 @@ ) { Pricing + + Pricing + @if (currentRoute !== routePricing && hasPromotion) { + % + } + + } Pricing + + Pricing + @if (currentRoute !== routePricing && hasPromotion) { + % + } + +
  • } @if (hasPermissionToAccessFearAndGreedIndex) { diff --git a/apps/client/src/app/components/header/header.component.ts b/apps/client/src/app/components/header/header.component.ts index 33069aa2..1739d113 100644 --- a/apps/client/src/app/components/header/header.component.ts +++ b/apps/client/src/app/components/header/header.component.ts @@ -58,6 +58,7 @@ export class HeaderComponent implements OnChanges { @Input() deviceType: string; @Input() hasPermissionToChangeDateRange: boolean; @Input() hasPermissionToChangeFilters: boolean; + @Input() hasPromotion: boolean; @Input() hasTabs: boolean; @Input() info: InfoItem; @Input() pageTitle: string; diff --git a/apps/client/src/app/components/user-account-membership/user-account-membership.component.ts b/apps/client/src/app/components/user-account-membership/user-account-membership.component.ts index 93bbe641..bde555d8 100644 --- a/apps/client/src/app/components/user-account-membership/user-account-membership.component.ts +++ b/apps/client/src/app/components/user-account-membership/user-account-membership.component.ts @@ -16,6 +16,7 @@ import { MatSnackBarRef, TextOnlySnackBar } from '@angular/material/snack-bar'; +import { StringValue } from 'ms'; import { StripeService } from 'ngx-stripe'; import { EMPTY, Subject } from 'rxjs'; import { catchError, switchMap, takeUntil } from 'rxjs/operators'; @@ -31,6 +32,7 @@ export class UserAccountMembershipComponent implements OnDestroy { public coupon: number; public couponId: string; public defaultDateFormat: string; + public durationExtension: StringValue; public hasPermissionForSubscription: boolean; public hasPermissionToUpdateUserSettings: boolean; public price: number; @@ -51,7 +53,7 @@ export class UserAccountMembershipComponent implements OnDestroy { private stripeService: StripeService, private userService: UserService ) { - const { baseCurrency, globalPermissions, subscriptions } = + const { baseCurrency, globalPermissions, subscriptionOffers } = this.dataService.fetchInfo(); this.baseCurrency = baseCurrency; @@ -76,11 +78,18 @@ export class UserAccountMembershipComponent implements OnDestroy { permissions.updateUserSettings ); - this.coupon = subscriptions?.[this.user.subscription.offer]?.coupon; + this.coupon = + subscriptionOffers?.[this.user.subscription.offer]?.coupon; this.couponId = - subscriptions?.[this.user.subscription.offer]?.couponId; - this.price = subscriptions?.[this.user.subscription.offer]?.price; - this.priceId = subscriptions?.[this.user.subscription.offer]?.priceId; + subscriptionOffers?.[this.user.subscription.offer]?.couponId; + this.durationExtension = + subscriptionOffers?.[ + this.user.subscription.offer + ]?.durationExtension; + this.price = + subscriptionOffers?.[this.user.subscription.offer]?.price; + this.priceId = + subscriptionOffers?.[this.user.subscription.offer]?.priceId; this.changeDetectorRef.markForCheck(); } diff --git a/apps/client/src/app/components/user-account-membership/user-account-membership.html b/apps/client/src/app/components/user-account-membership/user-account-membership.html index d30ce7bd..82b329a6 100644 --- a/apps/client/src/app/components/user-account-membership/user-account-membership.html +++ b/apps/client/src/app/components/user-account-membership/user-account-membership.html @@ -34,6 +34,16 @@  per year

    } + @if (durationExtension) { +
    +
    + Limited Offer! Get + {{ durationExtension }} extra +
    +
    + } }
    @if (!user?.subscription?.expiresAt) { diff --git a/apps/client/src/app/pages/pricing/pricing-page.component.ts b/apps/client/src/app/pages/pricing/pricing-page.component.ts index 8bd0f1bd..f86a7590 100644 --- a/apps/client/src/app/pages/pricing/pricing-page.component.ts +++ b/apps/client/src/app/pages/pricing/pricing-page.component.ts @@ -6,6 +6,7 @@ import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { translate } from '@ghostfolio/ui/i18n'; import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; +import { StringValue } from 'ms'; import { StripeService } from 'ngx-stripe'; import { Subject } from 'rxjs'; import { catchError, switchMap, takeUntil } from 'rxjs/operators'; @@ -20,6 +21,7 @@ export class PricingPageComponent implements OnDestroy, OnInit { public baseCurrency: string; public coupon: number; public couponId: string; + public durationExtension: StringValue; public hasPermissionToUpdateUserSettings: boolean; public importAndExportTooltipBasic = translate( 'DATA_IMPORT_AND_EXPORT_TOOLTIP_BASIC' @@ -51,11 +53,12 @@ export class PricingPageComponent implements OnDestroy, OnInit { ) {} public ngOnInit() { - const { baseCurrency, subscriptions } = this.dataService.fetchInfo(); + const { baseCurrency, subscriptionOffers } = this.dataService.fetchInfo(); this.baseCurrency = baseCurrency; - this.coupon = subscriptions?.default?.coupon; - this.price = subscriptions?.default?.price; + this.coupon = subscriptionOffers?.default?.coupon; + this.durationExtension = subscriptionOffers?.default?.durationExtension; + this.price = subscriptionOffers?.default?.price; this.userService.stateChanged .pipe(takeUntil(this.unsubscribeSubject)) @@ -68,11 +71,18 @@ export class PricingPageComponent implements OnDestroy, OnInit { permissions.updateUserSettings ); - this.coupon = subscriptions?.[this.user?.subscription?.offer]?.coupon; + this.coupon = + subscriptionOffers?.[this.user?.subscription?.offer]?.coupon; this.couponId = - subscriptions?.[this.user.subscription.offer]?.couponId; - this.price = subscriptions?.[this.user?.subscription?.offer]?.price; - this.priceId = subscriptions?.[this.user.subscription.offer]?.priceId; + subscriptionOffers?.[this.user.subscription.offer]?.couponId; + this.durationExtension = + subscriptionOffers?.[ + this.user?.subscription?.offer + ]?.durationExtension; + this.price = + subscriptionOffers?.[this.user?.subscription?.offer]?.price; + this.priceId = + subscriptionOffers?.[this.user.subscription.offer]?.priceId; this.changeDetectorRef.markForCheck(); } diff --git a/apps/client/src/app/pages/pricing/pricing-page.html b/apps/client/src/app/pages/pricing/pricing-page.html index fe805ef6..605ad5d2 100644 --- a/apps/client/src/app/pages/pricing/pricing-page.html +++ b/apps/client/src/app/pages/pricing/pricing-page.html @@ -101,6 +101,11 @@

    } + @if (durationExtension) { + + } @@ -159,6 +164,11 @@

    } + @if (durationExtension) { + + } @@ -289,6 +299,14 @@

    } + @if (durationExtension) { +
    +
    + Limited Offer! Get + {{ durationExtension }} extra +
    +
    + } diff --git a/apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts b/apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts index ea14bbc6..39dbc481 100644 --- a/apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts +++ b/apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -35,9 +35,9 @@ export class GfProductPageComponent implements OnInit { ) {} public ngOnInit() { - const { subscriptions } = this.dataService.fetchInfo(); + const { subscriptionOffers } = this.dataService.fetchInfo(); - this.price = subscriptions?.default?.price; + this.price = subscriptionOffers?.default?.price; this.product1 = { founded: 2021, diff --git a/libs/common/src/lib/interfaces/index.ts b/libs/common/src/lib/interfaces/index.ts index 0ec04594..becc872d 100644 --- a/libs/common/src/lib/interfaces/index.ts +++ b/libs/common/src/lib/interfaces/index.ts @@ -46,7 +46,7 @@ import type { PortfolioPerformanceResponse } from './responses/portfolio-perform import type { PublicPortfolioResponse } from './responses/public-portfolio-response.interface'; import type { ScraperConfiguration } from './scraper-configuration.interface'; import type { Statistics } from './statistics.interface'; -import type { Subscription } from './subscription.interface'; +import type { SubscriptionOffer } from './subscription-offer.interface'; import type { SymbolMetrics } from './symbol-metrics.interface'; import type { SystemMessage } from './system-message.interface'; import type { TabConfiguration } from './tab-configuration.interface'; @@ -102,8 +102,8 @@ export { ResponseError, ScraperConfiguration, Statistics, + SubscriptionOffer, SystemMessage, - Subscription, SymbolMetrics, TabConfiguration, ToggleOption, diff --git a/libs/common/src/lib/interfaces/info-item.interface.ts b/libs/common/src/lib/interfaces/info-item.interface.ts index 1b392633..bd3eb1f9 100644 --- a/libs/common/src/lib/interfaces/info-item.interface.ts +++ b/libs/common/src/lib/interfaces/info-item.interface.ts @@ -1,9 +1,9 @@ -import { SubscriptionOffer } from '@ghostfolio/common/types'; +import { SubscriptionOfferKey } from '@ghostfolio/common/types'; import { Platform, SymbolProfile } from '@prisma/client'; import { Statistics } from './statistics.interface'; -import { Subscription } from './subscription.interface'; +import { SubscriptionOffer } from './subscription-offer.interface'; export interface InfoItem { baseCurrency: string; @@ -18,5 +18,5 @@ export interface InfoItem { platforms: Platform[]; statistics: Statistics; stripePublicKey?: string; - subscriptions: { [offer in SubscriptionOffer]: Subscription }; + subscriptionOffers: { [offer in SubscriptionOfferKey]: SubscriptionOffer }; } diff --git a/libs/common/src/lib/interfaces/subscription-offer.interface.ts b/libs/common/src/lib/interfaces/subscription-offer.interface.ts new file mode 100644 index 00000000..8db91da6 --- /dev/null +++ b/libs/common/src/lib/interfaces/subscription-offer.interface.ts @@ -0,0 +1,9 @@ +import { StringValue } from 'ms'; + +export interface SubscriptionOffer { + coupon?: number; + couponId?: string; + durationExtension?: StringValue; + price: number; + priceId: string; +} diff --git a/libs/common/src/lib/interfaces/subscription.interface.ts b/libs/common/src/lib/interfaces/subscription.interface.ts deleted file mode 100644 index 29f5d3ab..00000000 --- a/libs/common/src/lib/interfaces/subscription.interface.ts +++ /dev/null @@ -1,6 +0,0 @@ -export interface Subscription { - coupon?: number; - couponId?: string; - price: number; - priceId: string; -} diff --git a/libs/common/src/lib/interfaces/user.interface.ts b/libs/common/src/lib/interfaces/user.interface.ts index 27cd1a61..647822d3 100644 --- a/libs/common/src/lib/interfaces/user.interface.ts +++ b/libs/common/src/lib/interfaces/user.interface.ts @@ -1,4 +1,4 @@ -import { SubscriptionOffer } from '@ghostfolio/common/types'; +import { SubscriptionOfferKey } from '@ghostfolio/common/types'; import { SubscriptionType } from '@ghostfolio/common/types/subscription-type.type'; import { Account, Tag } from '@prisma/client'; @@ -20,7 +20,7 @@ export interface User { systemMessage?: SystemMessage; subscription: { expiresAt?: Date; - offer: SubscriptionOffer; + offer: SubscriptionOfferKey; type: SubscriptionType; }; tags: (Tag & { isUsed: boolean })[]; diff --git a/libs/common/src/lib/types/index.ts b/libs/common/src/lib/types/index.ts index a66755ab..9e8178d3 100644 --- a/libs/common/src/lib/types/index.ts +++ b/libs/common/src/lib/types/index.ts @@ -15,7 +15,7 @@ import type { MarketState } from './market-state.type'; import type { Market } from './market.type'; import type { OrderWithAccount } from './order-with-account.type'; import type { RequestWithUser } from './request-with-user.type'; -import type { SubscriptionOffer } from './subscription-offer.type'; +import type { SubscriptionOfferKey } from './subscription-offer-key.type'; import type { UserWithSettings } from './user-with-settings.type'; import type { ViewMode } from './view-mode.type'; @@ -37,7 +37,7 @@ export type { MarketState, OrderWithAccount, RequestWithUser, - SubscriptionOffer, + SubscriptionOfferKey, UserWithSettings, ViewMode }; diff --git a/libs/common/src/lib/types/subscription-offer.type.ts b/libs/common/src/lib/types/subscription-offer-key.type.ts similarity index 71% rename from libs/common/src/lib/types/subscription-offer.type.ts rename to libs/common/src/lib/types/subscription-offer-key.type.ts index 98977da4..f6d898a0 100644 --- a/libs/common/src/lib/types/subscription-offer.type.ts +++ b/libs/common/src/lib/types/subscription-offer-key.type.ts @@ -1,4 +1,4 @@ -export type SubscriptionOffer = +export type SubscriptionOfferKey = | 'default' | 'renewal' | 'renewal-early-bird-2023' diff --git a/libs/common/src/lib/types/user-with-settings.type.ts b/libs/common/src/lib/types/user-with-settings.type.ts index 59e9f142..2a669d26 100644 --- a/libs/common/src/lib/types/user-with-settings.type.ts +++ b/libs/common/src/lib/types/user-with-settings.type.ts @@ -1,5 +1,5 @@ import { UserSettings } from '@ghostfolio/common/interfaces'; -import { SubscriptionOffer } from '@ghostfolio/common/types'; +import { SubscriptionOfferKey } from '@ghostfolio/common/types'; import { SubscriptionType } from '@ghostfolio/common/types/subscription-type.type'; import { Access, Account, Settings, User } from '@prisma/client'; @@ -13,7 +13,7 @@ export type UserWithSettings = User & { Settings: Settings & { settings: UserSettings }; subscription?: { expiresAt?: Date; - offer: SubscriptionOffer; + offer: SubscriptionOfferKey; type: SubscriptionType; }; }; From a80ca507f881b20fd7510893835eb733bf369ece Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 2 Nov 2024 13:45:26 +0100 Subject: [PATCH 12/65] Feature/add lastRequestAt to analytics (#4010) * Add lastRequestAt to Analytics --- apps/api/src/app/admin/admin.service.ts | 4 ++-- apps/api/src/app/auth/jwt.strategy.ts | 2 +- apps/api/src/app/info/info.service.ts | 2 +- .../migration.sql | 5 +++++ prisma/schema.prisma | 2 ++ 5 files changed, 11 insertions(+), 4 deletions(-) create mode 100644 prisma/migrations/20241102121004_added_last_request_at_to_analytics/migration.sql diff --git a/apps/api/src/app/admin/admin.service.ts b/apps/api/src/app/admin/admin.service.ts index 860f1985..49964c77 100644 --- a/apps/api/src/app/admin/admin.service.ts +++ b/apps/api/src/app/admin/admin.service.ts @@ -641,7 +641,7 @@ export class AdminService { } private async getUsersWithAnalytics(): Promise { - let orderBy: any = { + let orderBy: Prisma.UserOrderByWithRelationInput = { createdAt: 'desc' }; let where: Prisma.UserWhereInput; @@ -649,7 +649,7 @@ export class AdminService { if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) { orderBy = { Analytics: { - updatedAt: 'desc' + lastRequestAt: 'desc' } }; where = { diff --git a/apps/api/src/app/auth/jwt.strategy.ts b/apps/api/src/app/auth/jwt.strategy.ts index a8ad8fd0..1200ed46 100644 --- a/apps/api/src/app/auth/jwt.strategy.ts +++ b/apps/api/src/app/auth/jwt.strategy.ts @@ -46,7 +46,7 @@ export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') { update: { country, activityCount: { increment: 1 }, - updatedAt: new Date() + lastRequestAt: new Date() }, where: { userId: user.id } }); diff --git a/apps/api/src/app/info/info.service.ts b/apps/api/src/app/info/info.service.ts index f81ddd71..62a78d1d 100644 --- a/apps/api/src/app/info/info.service.ts +++ b/apps/api/src/app/info/info.service.ts @@ -142,7 +142,7 @@ export class InfoService { }, { Analytics: { - updatedAt: { + lastRequestAt: { gt: subDays(new Date(), aDays) } } diff --git a/prisma/migrations/20241102121004_added_last_request_at_to_analytics/migration.sql b/prisma/migrations/20241102121004_added_last_request_at_to_analytics/migration.sql new file mode 100644 index 00000000..b7af3103 --- /dev/null +++ b/prisma/migrations/20241102121004_added_last_request_at_to_analytics/migration.sql @@ -0,0 +1,5 @@ +-- AlterTable +ALTER TABLE "Analytics" ADD COLUMN "lastRequestAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP; + +-- CreateIndex +CREATE INDEX "Analytics_lastRequestAt_idx" ON "Analytics"("lastRequestAt"); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 7f386a71..5a34e8e1 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -68,10 +68,12 @@ model Analytics { activityCount Int @default(0) country String? dataProviderGhostfolioDailyRequests Int @default(0) + lastRequestAt DateTime @default(now()) updatedAt DateTime @updatedAt userId String @id User User @relation(fields: [userId], onDelete: Cascade, references: [id]) + @@index([lastRequestAt]) @@index([updatedAt]) } From 5652d19a88d81faeffac8b4c7e8a44521b172d79 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 2 Nov 2024 13:45:47 +0100 Subject: [PATCH 13/65] Feature/upgrade stripe dependencies 20241102 (#4009) * Upgrade stripe dependencies * Update changelog --- CHANGELOG.md | 1 + .../app/subscription/subscription.service.ts | 2 +- package-lock.json | 30 +++++++++---------- package.json | 6 ++-- 4 files changed, 20 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 397cc998..43238001 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Reverted the permissions (`chmod 0700`) on `entrypoint.sh` in the `Dockerfile` +- Upgraded the _Stripe_ dependencies ## 2.120.0 - 2024-10-30 diff --git a/apps/api/src/app/subscription/subscription.service.ts b/apps/api/src/app/subscription/subscription.service.ts index 47e6db00..ef73f346 100644 --- a/apps/api/src/app/subscription/subscription.service.ts +++ b/apps/api/src/app/subscription/subscription.service.ts @@ -31,7 +31,7 @@ export class SubscriptionService { this.stripe = new Stripe( this.configurationService.get('STRIPE_SECRET_KEY'), { - apiVersion: '2024-04-10' + apiVersion: '2024-09-30.acacia' } ); } diff --git a/package-lock.json b/package-lock.json index 0761fdf9..3e606f03 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ghostfolio", - "version": "2.119.0", + "version": "2.120.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ghostfolio", - "version": "2.119.0", + "version": "2.120.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { @@ -43,7 +43,7 @@ "@prisma/client": "5.21.1", "@simplewebauthn/browser": "9.0.1", "@simplewebauthn/server": "9.0.3", - "@stripe/stripe-js": "3.5.0", + "@stripe/stripe-js": "4.9.0", "alphavantage": "2.2.0", "big.js": "6.2.1", "body-parser": "1.20.2", @@ -78,7 +78,7 @@ "ngx-device-detector": "8.0.0", "ngx-markdown": "18.0.0", "ngx-skeleton-loader": "7.0.0", - "ngx-stripe": "18.0.0", + "ngx-stripe": "18.1.0", "open-color": "1.9.1", "papaparse": "5.3.1", "passport": "0.7.0", @@ -86,7 +86,7 @@ "passport-jwt": "4.0.1", "reflect-metadata": "0.1.13", "rxjs": "7.5.6", - "stripe": "15.11.0", + "stripe": "17.3.0", "svgmap": "2.6.0", "twitter-api-v2": "1.14.2", "uuid": "9.0.1", @@ -10334,9 +10334,9 @@ } }, "node_modules/@stripe/stripe-js": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-3.5.0.tgz", - "integrity": "sha512-pKS3wZnJoL1iTyGBXAvCwduNNeghJHY6QSRSNNvpYnrrQrLZ6Owsazjyynu0e0ObRgks0i7Rv+pe2M7/MBTZpQ==", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-4.9.0.tgz", + "integrity": "sha512-tMPZQZZXGWyNX7hbgenq+1xEj2oigJ54XddbtSX36VedoKsPBq7dxwRXu4Xd5FdpT3JDyyDtnmvYkaSnH1yHTQ==", "license": "MIT", "engines": { "node": ">=12.16" @@ -26872,9 +26872,9 @@ } }, "node_modules/ngx-stripe": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/ngx-stripe/-/ngx-stripe-18.0.0.tgz", - "integrity": "sha512-AT67vLeqEUDMnK5TfEaorumYJyOWqecbrh/1UWNtN8vF6Yzb0L/Dty3ANAa/QQi0OvBg6gXrudrhEnT8pT5lng==", + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/ngx-stripe/-/ngx-stripe-18.1.0.tgz", + "integrity": "sha512-fNWmFaCWWzfsr8GU9Bmi6fwgHZHMI9UwpV5M0HMvkANnz9n7JWjP2Uck6zk0lXdu9q989aIbqj4awbLCZk/TUw==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -26882,7 +26882,7 @@ "peerDependencies": { "@angular/common": ">=18.0.0 <19.0.0", "@angular/core": ">=18.0.0 <19.0.0", - "@stripe/stripe-js": ">=3.0.0 <4.0.0" + "@stripe/stripe-js": ">=4.0.0 <5.0.0" } }, "node_modules/nice-napi": { @@ -31883,9 +31883,9 @@ } }, "node_modules/stripe": { - "version": "15.11.0", - "resolved": "https://registry.npmjs.org/stripe/-/stripe-15.11.0.tgz", - "integrity": "sha512-qmZF0PN1jRVpiQrXL8eTb9Jy/6S+aUlcDquKBFT2h3PkaD7RZ444FIojVXUg67FK2zFIUNXgMv02c7csdL5qHg==", + "version": "17.3.0", + "resolved": "https://registry.npmjs.org/stripe/-/stripe-17.3.0.tgz", + "integrity": "sha512-WACmytj1MssbIwGwPfAomo61jgldb2B/cB6A3W/Bqs9zId1olVcAa8X7HERkqpw4190GSsbvrD7KnkZogatyvw==", "license": "MIT", "dependencies": { "@types/node": ">=8.1.0", diff --git a/package.json b/package.json index 134d72f9..0cb2a473 100644 --- a/package.json +++ b/package.json @@ -89,7 +89,7 @@ "@prisma/client": "5.21.1", "@simplewebauthn/browser": "9.0.1", "@simplewebauthn/server": "9.0.3", - "@stripe/stripe-js": "3.5.0", + "@stripe/stripe-js": "4.9.0", "alphavantage": "2.2.0", "big.js": "6.2.1", "body-parser": "1.20.2", @@ -124,7 +124,7 @@ "ngx-device-detector": "8.0.0", "ngx-markdown": "18.0.0", "ngx-skeleton-loader": "7.0.0", - "ngx-stripe": "18.0.0", + "ngx-stripe": "18.1.0", "open-color": "1.9.1", "papaparse": "5.3.1", "passport": "0.7.0", @@ -132,7 +132,7 @@ "passport-jwt": "4.0.1", "reflect-metadata": "0.1.13", "rxjs": "7.5.6", - "stripe": "15.11.0", + "stripe": "17.3.0", "svgmap": "2.6.0", "twitter-api-v2": "1.14.2", "uuid": "9.0.1", From de74d5c3d6ce859b752640ef77d6192828561ece Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 2 Nov 2024 13:48:03 +0100 Subject: [PATCH 14/65] Release 2.121.0 (#4011) --- CHANGELOG.md | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 43238001..89705c83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ 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.121.0 - 2024-11-02 ### Added diff --git a/package-lock.json b/package-lock.json index 3e606f03..861cf797 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ghostfolio", - "version": "2.120.0", + "version": "2.121.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ghostfolio", - "version": "2.120.0", + "version": "2.121.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/package.json b/package.json index 0cb2a473..53d5db44 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ghostfolio", - "version": "2.120.0", + "version": "2.121.0", "homepage": "https://ghostfol.io", "license": "AGPL-3.0", "repository": "https://github.com/ghostfolio/ghostfolio", From b2aa31f4ba57d6fb3d371108755ddc539cb257d3 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 2 Nov 2024 22:28:13 +0100 Subject: [PATCH 15/65] Bugfix/handle missing Stripe api key exception (#4013) * Conditionally initialize Stripe --- .../src/app/subscription/subscription.service.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/apps/api/src/app/subscription/subscription.service.ts b/apps/api/src/app/subscription/subscription.service.ts index ef73f346..ae0260d8 100644 --- a/apps/api/src/app/subscription/subscription.service.ts +++ b/apps/api/src/app/subscription/subscription.service.ts @@ -28,12 +28,14 @@ export class SubscriptionService { private readonly prismaService: PrismaService, private readonly propertyService: PropertyService ) { - this.stripe = new Stripe( - this.configurationService.get('STRIPE_SECRET_KEY'), - { - apiVersion: '2024-09-30.acacia' - } - ); + if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) { + this.stripe = new Stripe( + this.configurationService.get('STRIPE_SECRET_KEY'), + { + apiVersion: '2024-09-30.acacia' + } + ); + } } public async createCheckoutSession({ From c857bd1b2e51aff7eb83e667b5f6b85ac41a604f Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 2 Nov 2024 22:29:28 +0100 Subject: [PATCH 16/65] Release 2.121.1 (#4014) --- CHANGELOG.md | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89705c83..5fff96a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ 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). -## 2.121.0 - 2024-11-02 +## 2.121.1 - 2024-11-02 ### Added diff --git a/package-lock.json b/package-lock.json index 861cf797..68e0a178 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ghostfolio", - "version": "2.121.0", + "version": "2.121.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ghostfolio", - "version": "2.121.0", + "version": "2.121.1", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/package.json b/package.json index 53d5db44..f3c7ad67 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ghostfolio", - "version": "2.121.0", + "version": "2.121.1", "homepage": "https://ghostfol.io", "license": "AGPL-3.0", "repository": "https://github.com/ghostfolio/ghostfolio", From a1fbdc2ebea32d3dab258014681e63b8e3af09d9 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 3 Nov 2024 15:51:59 +0100 Subject: [PATCH 17/65] Bugfix/exception handling in user authorization (#4015) * Add guard * Update changelog --- CHANGELOG.md | 6 ++++++ apps/api/src/app/auth/jwt.strategy.ts | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fff96a3..f675e03d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ 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 + +### Fixed + +- Improved the exception handling in the user authorization service + ## 2.121.1 - 2024-11-02 ### Added diff --git a/apps/api/src/app/auth/jwt.strategy.ts b/apps/api/src/app/auth/jwt.strategy.ts index 1200ed46..7a3fb224 100644 --- a/apps/api/src/app/auth/jwt.strategy.ts +++ b/apps/api/src/app/auth/jwt.strategy.ts @@ -60,7 +60,7 @@ export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') { ); } } catch (error) { - if (error?.getStatus() === StatusCodes.TOO_MANY_REQUESTS) { + if (error?.getStatus?.() === StatusCodes.TOO_MANY_REQUESTS) { throw error; } else { throw new HttpException( From 93001b68ad0efbc79f2bf0b26ed34e6715950a95 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 3 Nov 2024 19:58:19 +0100 Subject: [PATCH 18/65] Feature/introduce lookup response interface (#4019) * Introduce lookup response interface * Refactor lookup item interface --- apps/api/src/app/symbol/symbol.controller.ts | 4 ++-- apps/api/src/app/symbol/symbol.service.ts | 10 ++++++---- .../alpha-vantage/alpha-vantage.service.ts | 10 +++++----- .../data-provider/coingecko/coingecko.service.ts | 11 ++++++----- .../services/data-provider/data-provider.service.ts | 11 +++++++---- .../eod-historical-data.service.ts | 11 ++++++----- .../financial-modeling-prep.service.ts | 11 ++++++----- .../google-sheets/google-sheets.service.ts | 10 +++++----- .../interfaces/data-provider.interface.ts | 11 +++++------ .../services/data-provider/manual/manual.service.ts | 6 ++---- .../data-provider/rapid-api/rapid-api.service.ts | 8 +++++--- .../yahoo-finance/yahoo-finance.service.ts | 9 ++++++--- apps/client/src/app/services/data.service.ts | 4 ++-- libs/common/src/lib/interfaces/index.ts | 4 ++++ .../src/lib}/interfaces/lookup-item.interface.ts | 4 ++-- .../interfaces/responses/lookup-response.interface.ts | 5 +++++ .../symbol-autocomplete.component.ts | 2 +- 17 files changed, 75 insertions(+), 56 deletions(-) rename {apps/api/src/app/symbol => libs/common/src/lib}/interfaces/lookup-item.interface.ts (80%) create mode 100644 libs/common/src/lib/interfaces/responses/lookup-response.interface.ts diff --git a/apps/api/src/app/symbol/symbol.controller.ts b/apps/api/src/app/symbol/symbol.controller.ts index b3b9dc10..89cdd416 100644 --- a/apps/api/src/app/symbol/symbol.controller.ts +++ b/apps/api/src/app/symbol/symbol.controller.ts @@ -2,6 +2,7 @@ import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard' import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.interceptor'; import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor'; import { IDataProviderHistoricalResponse } from '@ghostfolio/api/services/interfaces/interfaces'; +import { LookupResponse } from '@ghostfolio/common/interfaces'; import type { RequestWithUser } from '@ghostfolio/common/types'; import { @@ -21,7 +22,6 @@ import { parseISO } from 'date-fns'; import { StatusCodes, getReasonPhrase } from 'http-status-codes'; import { isDate, isEmpty } from 'lodash'; -import { LookupItem } from './interfaces/lookup-item.interface'; import { SymbolItem } from './interfaces/symbol-item.interface'; import { SymbolService } from './symbol.service'; @@ -41,7 +41,7 @@ export class SymbolController { public async lookupSymbol( @Query('includeIndices') includeIndicesParam = 'false', @Query('query') query = '' - ): Promise<{ items: LookupItem[] }> { + ): Promise { const includeIndices = includeIndicesParam === 'true'; try { diff --git a/apps/api/src/app/symbol/symbol.service.ts b/apps/api/src/app/symbol/symbol.service.ts index 2baca18d..ae864e2f 100644 --- a/apps/api/src/app/symbol/symbol.service.ts +++ b/apps/api/src/app/symbol/symbol.service.ts @@ -5,13 +5,15 @@ import { } from '@ghostfolio/api/services/interfaces/interfaces'; import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service'; import { DATE_FORMAT } from '@ghostfolio/common/helper'; -import { HistoricalDataItem } from '@ghostfolio/common/interfaces'; +import { + HistoricalDataItem, + LookupResponse +} from '@ghostfolio/common/interfaces'; import { UserWithSettings } from '@ghostfolio/common/types'; import { Injectable, Logger } from '@nestjs/common'; import { format, subDays } from 'date-fns'; -import { LookupItem } from './interfaces/lookup-item.interface'; import { SymbolItem } from './interfaces/symbol-item.interface'; @Injectable() @@ -104,8 +106,8 @@ export class SymbolService { includeIndices?: boolean; query: string; user: UserWithSettings; - }): Promise<{ items: LookupItem[] }> { - const results: { items: LookupItem[] } = { items: [] }; + }): Promise { + const results: LookupResponse = { items: [] }; if (!query) { return results; diff --git a/apps/api/src/services/data-provider/alpha-vantage/alpha-vantage.service.ts b/apps/api/src/services/data-provider/alpha-vantage/alpha-vantage.service.ts index 01658494..5c9eee12 100644 --- a/apps/api/src/services/data-provider/alpha-vantage/alpha-vantage.service.ts +++ b/apps/api/src/services/data-provider/alpha-vantage/alpha-vantage.service.ts @@ -1,4 +1,3 @@ -import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { DataProviderInterface, @@ -12,7 +11,10 @@ import { IDataProviderResponse } from '@ghostfolio/api/services/interfaces/interfaces'; import { DATE_FORMAT } from '@ghostfolio/common/helper'; -import { DataProviderInfo } from '@ghostfolio/common/interfaces'; +import { + DataProviderInfo, + LookupResponse +} from '@ghostfolio/common/interfaces'; import { Injectable } from '@nestjs/common'; import { DataSource, SymbolProfile } from '@prisma/client'; @@ -119,9 +121,7 @@ export class AlphaVantageService implements DataProviderInterface { return undefined; } - public async search({ - query - }: GetSearchParams): Promise<{ items: LookupItem[] }> { + public async search({ query }: GetSearchParams): Promise { const result = await this.alphaVantage.data.search(query); return { diff --git a/apps/api/src/services/data-provider/coingecko/coingecko.service.ts b/apps/api/src/services/data-provider/coingecko/coingecko.service.ts index d420c51f..7d6f22c6 100644 --- a/apps/api/src/services/data-provider/coingecko/coingecko.service.ts +++ b/apps/api/src/services/data-provider/coingecko/coingecko.service.ts @@ -1,4 +1,3 @@ -import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { DataProviderInterface, @@ -13,7 +12,11 @@ import { } from '@ghostfolio/api/services/interfaces/interfaces'; import { DEFAULT_CURRENCY } from '@ghostfolio/common/config'; import { DATE_FORMAT } from '@ghostfolio/common/helper'; -import { DataProviderInfo } from '@ghostfolio/common/interfaces'; +import { + DataProviderInfo, + LookupItem, + LookupResponse +} from '@ghostfolio/common/interfaces'; import { Injectable, Logger } from '@nestjs/common'; import { @@ -221,9 +224,7 @@ export class CoinGeckoService implements DataProviderInterface { return 'bitcoin'; } - public async search({ - query - }: GetSearchParams): Promise<{ items: LookupItem[] }> { + public async search({ query }: GetSearchParams): Promise { let items: LookupItem[] = []; try { diff --git a/apps/api/src/services/data-provider/data-provider.service.ts b/apps/api/src/services/data-provider/data-provider.service.ts index 385c7b07..c4670bc3 100644 --- a/apps/api/src/services/data-provider/data-provider.service.ts +++ b/apps/api/src/services/data-provider/data-provider.service.ts @@ -1,5 +1,4 @@ import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service'; -import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { DataProviderInterface } from '@ghostfolio/api/services/data-provider/interfaces/data-provider.interface'; import { @@ -20,7 +19,11 @@ import { getStartOfUtcDate, isDerivedCurrency } from '@ghostfolio/common/helper'; -import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces'; +import { + AssetProfileIdentifier, + LookupItem, + LookupResponse +} from '@ghostfolio/common/interfaces'; import type { Granularity, UserWithSettings } from '@ghostfolio/common/types'; import { Inject, Injectable, Logger } from '@nestjs/common'; @@ -571,8 +574,8 @@ export class DataProviderService { includeIndices?: boolean; query: string; user: UserWithSettings; - }): Promise<{ items: LookupItem[] }> { - const promises: Promise<{ items: LookupItem[] }>[] = []; + }): Promise { + const promises: Promise[] = []; let lookupItems: LookupItem[] = []; if (query?.length < 2) { diff --git a/apps/api/src/services/data-provider/eod-historical-data/eod-historical-data.service.ts b/apps/api/src/services/data-provider/eod-historical-data/eod-historical-data.service.ts index c3c948b4..7329b821 100644 --- a/apps/api/src/services/data-provider/eod-historical-data/eod-historical-data.service.ts +++ b/apps/api/src/services/data-provider/eod-historical-data/eod-historical-data.service.ts @@ -1,4 +1,3 @@ -import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { DataProviderInterface, @@ -17,7 +16,11 @@ import { REPLACE_NAME_PARTS } from '@ghostfolio/common/config'; import { DATE_FORMAT, isCurrency } from '@ghostfolio/common/helper'; -import { DataProviderInfo } from '@ghostfolio/common/interfaces'; +import { + DataProviderInfo, + LookupItem, + LookupResponse +} from '@ghostfolio/common/interfaces'; import { MarketState } from '@ghostfolio/common/types'; import { Injectable, Logger } from '@nestjs/common'; @@ -317,9 +320,7 @@ export class EodHistoricalDataService implements DataProviderInterface { return 'AAPL.US'; } - public async search({ - query - }: GetSearchParams): Promise<{ items: LookupItem[] }> { + public async search({ query }: GetSearchParams): Promise { const searchResult = await this.getSearchResult(query); return { diff --git a/apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts b/apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts index 7d5b3847..9334fc4c 100644 --- a/apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts +++ b/apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts @@ -1,4 +1,3 @@ -import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { DataProviderInterface, @@ -13,7 +12,11 @@ import { } from '@ghostfolio/api/services/interfaces/interfaces'; import { DEFAULT_CURRENCY } from '@ghostfolio/common/config'; import { DATE_FORMAT, parseDate } from '@ghostfolio/common/helper'; -import { DataProviderInfo } from '@ghostfolio/common/interfaces'; +import { + DataProviderInfo, + LookupItem, + LookupResponse +} from '@ghostfolio/common/interfaces'; import { Injectable, Logger } from '@nestjs/common'; import { DataSource, SymbolProfile } from '@prisma/client'; @@ -169,9 +172,7 @@ export class FinancialModelingPrepService implements DataProviderInterface { return 'AAPL'; } - public async search({ - query - }: GetSearchParams): Promise<{ items: LookupItem[] }> { + public async search({ query }: GetSearchParams): Promise { let items: LookupItem[] = []; try { diff --git a/apps/api/src/services/data-provider/google-sheets/google-sheets.service.ts b/apps/api/src/services/data-provider/google-sheets/google-sheets.service.ts index 9f234423..f18d670d 100644 --- a/apps/api/src/services/data-provider/google-sheets/google-sheets.service.ts +++ b/apps/api/src/services/data-provider/google-sheets/google-sheets.service.ts @@ -1,4 +1,3 @@ -import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { DataProviderInterface, @@ -14,7 +13,10 @@ import { import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service'; import { DATE_FORMAT, parseDate } from '@ghostfolio/common/helper'; -import { DataProviderInfo } from '@ghostfolio/common/interfaces'; +import { + DataProviderInfo, + LookupResponse +} from '@ghostfolio/common/interfaces'; import { Injectable, Logger } from '@nestjs/common'; import { DataSource, SymbolProfile } from '@prisma/client'; @@ -157,9 +159,7 @@ export class GoogleSheetsService implements DataProviderInterface { return 'INDEXSP:.INX'; } - public async search({ - query - }: GetSearchParams): Promise<{ items: LookupItem[] }> { + public async search({ query }: GetSearchParams): Promise { const items = await this.prismaService.symbolProfile.findMany({ select: { assetClass: true, diff --git a/apps/api/src/services/data-provider/interfaces/data-provider.interface.ts b/apps/api/src/services/data-provider/interfaces/data-provider.interface.ts index 3b364447..7352ce78 100644 --- a/apps/api/src/services/data-provider/interfaces/data-provider.interface.ts +++ b/apps/api/src/services/data-provider/interfaces/data-provider.interface.ts @@ -1,9 +1,11 @@ -import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface'; import { IDataProviderHistoricalResponse, IDataProviderResponse } from '@ghostfolio/api/services/interfaces/interfaces'; -import { DataProviderInfo } from '@ghostfolio/common/interfaces'; +import { + DataProviderInfo, + LookupResponse +} from '@ghostfolio/common/interfaces'; import { Granularity } from '@ghostfolio/common/types'; import { DataSource, SymbolProfile } from '@prisma/client'; @@ -44,10 +46,7 @@ export interface DataProviderInterface { getTestSymbol(): string; - search({ - includeIndices, - query - }: GetSearchParams): Promise<{ items: LookupItem[] }>; + search({ includeIndices, query }: GetSearchParams): Promise; } export interface GetDividendsParams { diff --git a/apps/api/src/services/data-provider/manual/manual.service.ts b/apps/api/src/services/data-provider/manual/manual.service.ts index 030ab8ea..30c7efa6 100644 --- a/apps/api/src/services/data-provider/manual/manual.service.ts +++ b/apps/api/src/services/data-provider/manual/manual.service.ts @@ -1,4 +1,3 @@ -import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { DataProviderInterface, @@ -20,6 +19,7 @@ import { } from '@ghostfolio/common/helper'; import { DataProviderInfo, + LookupResponse, ScraperConfiguration } from '@ghostfolio/common/interfaces'; @@ -219,9 +219,7 @@ export class ManualService implements DataProviderInterface { return undefined; } - public async search({ - query - }: GetSearchParams): Promise<{ items: LookupItem[] }> { + public async search({ query }: GetSearchParams): Promise { let items = await this.prismaService.symbolProfile.findMany({ select: { assetClass: true, diff --git a/apps/api/src/services/data-provider/rapid-api/rapid-api.service.ts b/apps/api/src/services/data-provider/rapid-api/rapid-api.service.ts index e47e96d8..29e7f4ee 100644 --- a/apps/api/src/services/data-provider/rapid-api/rapid-api.service.ts +++ b/apps/api/src/services/data-provider/rapid-api/rapid-api.service.ts @@ -1,4 +1,3 @@ -import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface'; import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { DataProviderInterface, @@ -13,7 +12,10 @@ import { } from '@ghostfolio/api/services/interfaces/interfaces'; import { ghostfolioFearAndGreedIndexSymbol } from '@ghostfolio/common/config'; import { DATE_FORMAT, getYesterday } from '@ghostfolio/common/helper'; -import { DataProviderInfo } from '@ghostfolio/common/interfaces'; +import { + DataProviderInfo, + LookupResponse +} from '@ghostfolio/common/interfaces'; import { Injectable, Logger } from '@nestjs/common'; import { DataSource, SymbolProfile } from '@prisma/client'; @@ -121,7 +123,7 @@ export class RapidApiService implements DataProviderInterface { return undefined; } - public async search({}: GetSearchParams): Promise<{ items: LookupItem[] }> { + public async search({}: GetSearchParams): Promise { return { items: [] }; } diff --git a/apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts b/apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts index 2d67c646..27da18ab 100644 --- a/apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts +++ b/apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts @@ -1,4 +1,3 @@ -import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface'; import { CryptocurrencyService } from '@ghostfolio/api/services/cryptocurrency/cryptocurrency.service'; import { YahooFinanceDataEnhancerService } from '@ghostfolio/api/services/data-provider/data-enhancer/yahoo-finance/yahoo-finance.service'; import { @@ -14,7 +13,11 @@ import { } from '@ghostfolio/api/services/interfaces/interfaces'; import { DEFAULT_CURRENCY } from '@ghostfolio/common/config'; import { DATE_FORMAT } from '@ghostfolio/common/helper'; -import { DataProviderInfo } from '@ghostfolio/common/interfaces'; +import { + DataProviderInfo, + LookupItem, + LookupResponse +} from '@ghostfolio/common/interfaces'; import { Injectable, Logger } from '@nestjs/common'; import { DataSource, SymbolProfile } from '@prisma/client'; @@ -224,7 +227,7 @@ export class YahooFinanceService implements DataProviderInterface { public async search({ includeIndices = false, query - }: GetSearchParams): Promise<{ items: LookupItem[] }> { + }: GetSearchParams): Promise { const items: LookupItem[] = []; try { diff --git a/apps/client/src/app/services/data.service.ts b/apps/client/src/app/services/data.service.ts index abf4b21a..1d40de69 100644 --- a/apps/client/src/app/services/data.service.ts +++ b/apps/client/src/app/services/data.service.ts @@ -10,7 +10,6 @@ import { } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { UpdateOrderDto } from '@ghostfolio/api/app/order/update-order.dto'; import { PortfolioHoldingDetail } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-holding-detail.interface'; -import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface'; import { SymbolItem } from '@ghostfolio/api/app/symbol/interfaces/symbol-item.interface'; import { DeleteOwnUserDto } from '@ghostfolio/api/app/user/delete-own-user.dto'; import { UserItem } from '@ghostfolio/api/app/user/interfaces/user-item.interface'; @@ -30,6 +29,7 @@ import { Filter, ImportResponse, InfoItem, + LookupResponse, OAuthResponse, PortfolioDetails, PortfolioDividends, @@ -464,7 +464,7 @@ export class DataService { } return this.http - .get<{ items: LookupItem[] }>('/api/v1/symbol/lookup', { params }) + .get('/api/v1/symbol/lookup', { params }) .pipe( map((respose) => { return respose.items; diff --git a/libs/common/src/lib/interfaces/index.ts b/libs/common/src/lib/interfaces/index.ts index becc872d..eca14706 100644 --- a/libs/common/src/lib/interfaces/index.ts +++ b/libs/common/src/lib/interfaces/index.ts @@ -23,6 +23,7 @@ import type { Holding } from './holding.interface'; import type { InfoItem } from './info-item.interface'; import type { InvestmentItem } from './investment-item.interface'; import type { LineChartItem } from './line-chart-item.interface'; +import type { LookupItem } from './lookup-item.interface'; import type { PortfolioChart } from './portfolio-chart.interface'; import type { PortfolioDetails } from './portfolio-details.interface'; import type { PortfolioDividends } from './portfolio-dividends.interface'; @@ -40,6 +41,7 @@ import type { AccountBalancesResponse } from './responses/account-balances-respo import type { BenchmarkResponse } from './responses/benchmark-response.interface'; import type { ResponseError } from './responses/errors.interface'; import type { ImportResponse } from './responses/import-response.interface'; +import type { LookupResponse } from './responses/lookup-response.interface'; import type { OAuthResponse } from './responses/oauth-response.interface'; import type { PortfolioHoldingsResponse } from './responses/portfolio-holdings-response.interface'; import type { PortfolioPerformanceResponse } from './responses/portfolio-performance-response.interface'; @@ -82,6 +84,8 @@ export { InfoItem, InvestmentItem, LineChartItem, + LookupItem, + LookupResponse, OAuthResponse, PortfolioChart, PortfolioDetails, diff --git a/apps/api/src/app/symbol/interfaces/lookup-item.interface.ts b/libs/common/src/lib/interfaces/lookup-item.interface.ts similarity index 80% rename from apps/api/src/app/symbol/interfaces/lookup-item.interface.ts rename to libs/common/src/lib/interfaces/lookup-item.interface.ts index 89b47639..fa91ed69 100644 --- a/apps/api/src/app/symbol/interfaces/lookup-item.interface.ts +++ b/libs/common/src/lib/interfaces/lookup-item.interface.ts @@ -1,7 +1,7 @@ -import { DataProviderInfo } from '@ghostfolio/common/interfaces'; - import { AssetClass, AssetSubClass, DataSource } from '@prisma/client'; +import { DataProviderInfo } from './data-provider-info.interface'; + export interface LookupItem { assetClass: AssetClass; assetSubClass: AssetSubClass; diff --git a/libs/common/src/lib/interfaces/responses/lookup-response.interface.ts b/libs/common/src/lib/interfaces/responses/lookup-response.interface.ts new file mode 100644 index 00000000..579be9d0 --- /dev/null +++ b/libs/common/src/lib/interfaces/responses/lookup-response.interface.ts @@ -0,0 +1,5 @@ +import { LookupItem } from '../lookup-item.interface'; + +export interface LookupResponse { + items: LookupItem[]; +} diff --git a/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.ts b/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.ts index da97aac0..a537c50a 100644 --- a/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.ts +++ b/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.ts @@ -1,6 +1,6 @@ -import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface'; import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module'; import { DataService } from '@ghostfolio/client/services/data.service'; +import { LookupItem } from '@ghostfolio/common/interfaces'; import { translate } from '@ghostfolio/ui/i18n'; import { AbstractMatFormField } from '@ghostfolio/ui/shared/abstract-mat-form-field'; From 316b7e82f1a9a8e03819e8339456826c7bfbe3e1 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 4 Nov 2024 19:05:01 +0100 Subject: [PATCH 19/65] Feature/upgrade countries-list to version 3.1.1 (#4020) * Upgrade countries-list to version 3.1.1 * Update changelog --- CHANGELOG.md | 4 ++++ package-lock.json | 8 ++++---- package.json | 2 +- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f675e03d..2ae5de6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Changed + +- Upgraded `countries-list` from version `3.1.0` to `3.1.1` + ### Fixed - Improved the exception handling in the user authorization service diff --git a/package-lock.json b/package-lock.json index 68e0a178..9dfb1f59 100644 --- a/package-lock.json +++ b/package-lock.json @@ -61,7 +61,7 @@ "class-validator": "0.14.1", "color": "4.2.3", "countries-and-timezones": "3.4.1", - "countries-list": "3.1.0", + "countries-list": "3.1.1", "countup.js": "2.8.0", "date-fns": "3.6.0", "envalid": "7.3.1", @@ -15243,9 +15243,9 @@ } }, "node_modules/countries-list": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/countries-list/-/countries-list-3.1.0.tgz", - "integrity": "sha512-HpTBLZba1VPTZSjUnUwR7SykxV7Z/7/+ZM5x5wi5tO99Qvom6bE2SC+AQ18016ujg3jSlYBbMITrHNnPAHSM9Q==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/countries-list/-/countries-list-3.1.1.tgz", + "integrity": "sha512-nPklKJ5qtmY5MdBKw1NiBAoyx5Sa7p2yPpljZyQ7gyCN1m+eMFs9I6CT37Mxt8zvR5L3VzD3DJBE4WQzX3WF4A==", "license": "MIT" }, "node_modules/countup.js": { diff --git a/package.json b/package.json index f3c7ad67..06bee046 100644 --- a/package.json +++ b/package.json @@ -107,7 +107,7 @@ "class-validator": "0.14.1", "color": "4.2.3", "countries-and-timezones": "3.4.1", - "countries-list": "3.1.0", + "countries-list": "3.1.1", "countup.js": "2.8.0", "date-fns": "3.6.0", "envalid": "7.3.1", From 9c27fb70aaaef5abe225aef024a65b9fcbcab652 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Tue, 5 Nov 2024 13:01:53 +0100 Subject: [PATCH 20/65] Feature/minor improvements in data provider service (#4017) * Refactoring --- .../api/src/services/data-provider/data-provider.service.ts | 6 +++--- apps/client/src/app/services/data.service.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/api/src/services/data-provider/data-provider.service.ts b/apps/api/src/services/data-provider/data-provider.service.ts index c4670bc3..bf23f96c 100644 --- a/apps/api/src/services/data-provider/data-provider.service.ts +++ b/apps/api/src/services/data-provider/data-provider.service.ts @@ -575,8 +575,8 @@ export class DataProviderService { query: string; user: UserWithSettings; }): Promise { - const promises: Promise[] = []; let lookupItems: LookupItem[] = []; + const promises: Promise[] = []; if (query?.length < 2) { return { items: lookupItems }; @@ -606,9 +606,9 @@ export class DataProviderService { }); const filteredItems = lookupItems - .filter((lookupItem) => { + .filter(({ currency }) => { // Only allow symbols with supported currency - return lookupItem.currency ? true : false; + return currency ? true : false; }) .sort(({ name: name1 }, { name: name2 }) => { return name1?.toLowerCase().localeCompare(name2?.toLowerCase()); diff --git a/apps/client/src/app/services/data.service.ts b/apps/client/src/app/services/data.service.ts index 1d40de69..cde7555b 100644 --- a/apps/client/src/app/services/data.service.ts +++ b/apps/client/src/app/services/data.service.ts @@ -466,8 +466,8 @@ export class DataService { return this.http .get('/api/v1/symbol/lookup', { params }) .pipe( - map((respose) => { - return respose.items; + map(({ items }) => { + return items; }) ); } From 04d5416c6ded25d53818bc890e599852d16ea1d2 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 6 Nov 2024 18:09:44 +0100 Subject: [PATCH 21/65] Feature/harmonize date formats in import test files (#3910) * Harmonize date formats --- test/import/ok-derived-currency.json | 2 +- test/import/ok-vti-buy-long-history.json | 6 +++--- test/import/ok-without-accounts.json | 8 ++++---- test/import/ok.json | 10 +++++----- test/import/unavailable-exchange-rate.json | 2 +- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/test/import/ok-derived-currency.json b/test/import/ok-derived-currency.json index b43be395..fe5554be 100644 --- a/test/import/ok-derived-currency.json +++ b/test/import/ok-derived-currency.json @@ -23,7 +23,7 @@ "unitPrice": 10875.00, "currency": "ZAc", "dataSource": "YAHOO", - "date": "2024-06-27T22:00:00.000Z", + "date": "2024-06-28T00:00:00.000Z", "symbol": "JSE.JO" } ] diff --git a/test/import/ok-vti-buy-long-history.json b/test/import/ok-vti-buy-long-history.json index e6020e40..1586cff7 100644 --- a/test/import/ok-vti-buy-long-history.json +++ b/test/import/ok-vti-buy-long-history.json @@ -11,7 +11,7 @@ "unitPrice": 65.31, "currency": "USD", "dataSource": "YAHOO", - "date": "2012-01-02T22:00:00.000Z", + "date": "2012-01-03T00:00:00.000Z", "symbol": "VTI" }, { @@ -21,7 +21,7 @@ "unitPrice": 65.40, "currency": "USD", "dataSource": "YAHOO", - "date": "2011-01-02T22:00:00.000Z", + "date": "2011-01-03T00:00:00.000Z", "symbol": "VTI" }, { @@ -31,7 +31,7 @@ "unitPrice": 57.05, "currency": "USD", "dataSource": "YAHOO", - "date": "2010-01-03T22:00:00.000Z", + "date": "2010-01-04T00:00:00.000Z", "symbol": "VTI" } ] diff --git a/test/import/ok-without-accounts.json b/test/import/ok-without-accounts.json index 63961be7..2ba0925b 100644 --- a/test/import/ok-without-accounts.json +++ b/test/import/ok-without-accounts.json @@ -11,7 +11,7 @@ "unitPrice": 0, "currency": "USD", "dataSource": "YAHOO", - "date": "2050-06-05T22:00:00.000Z", + "date": "2050-06-06T00:00:00.000Z", "symbol": "MSFT" }, { @@ -21,7 +21,7 @@ "unitPrice": 500000, "currency": "USD", "dataSource": "MANUAL", - "date": "2021-12-31T22:00:00.000Z", + "date": "2022-01-01T00:00:00.000Z", "symbol": "Penthouse Apartment" }, { @@ -31,7 +31,7 @@ "unitPrice": 0.62, "currency": "USD", "dataSource": "YAHOO", - "date": "2021-11-16T22:00:00.000Z", + "date": "2021-11-17T00:00:00.000Z", "symbol": "MSFT" }, { @@ -41,7 +41,7 @@ "unitPrice": 298.58, "currency": "USD", "dataSource": "YAHOO", - "date": "2021-09-15T22:00:00.000Z", + "date": "2021-09-16T00:00:00.000Z", "symbol": "MSFT" } ] diff --git a/test/import/ok.json b/test/import/ok.json index 0af28550..4bce98ba 100644 --- a/test/import/ok.json +++ b/test/import/ok.json @@ -23,7 +23,7 @@ "unitPrice": 0, "currency": "USD", "dataSource": "YAHOO", - "date": "2050-06-05T22:00:00.000Z", + "date": "2050-06-06T00:00:00.000Z", "symbol": "US5949181045" }, { @@ -35,7 +35,7 @@ "unitPrice": 500000, "currency": "USD", "dataSource": "MANUAL", - "date": "2021-12-31T22:00:00.000Z", + "date": "2022-01-01T00:00:00.000Z", "symbol": "Penthouse Apartment" }, { @@ -47,7 +47,7 @@ "unitPrice": 0.62, "currency": "USD", "dataSource": "YAHOO", - "date": "2021-11-16T22:00:00.000Z", + "date": "2021-11-17T00:00:00.000Z", "symbol": "MSFT" }, { @@ -59,7 +59,7 @@ "unitPrice": 298.58, "currency": "USD", "dataSource": "YAHOO", - "date": "2021-09-15T22:00:00.000Z", + "date": "2021-09-16T00:00:00.000Z", "symbol": "MSFT" }, { @@ -71,7 +71,7 @@ "unitPrice": 0, "currency": "USD", "dataSource": "MANUAL", - "date": "2021-08-31T22:00:00.000Z", + "date": "2021-09-01T00:00:00.000Z", "symbol": "Account Opening Fee" } ] diff --git a/test/import/unavailable-exchange-rate.json b/test/import/unavailable-exchange-rate.json index 2d21a76c..4d8be156 100644 --- a/test/import/unavailable-exchange-rate.json +++ b/test/import/unavailable-exchange-rate.json @@ -12,7 +12,7 @@ "unitPrice": 0, "currency": "EUR", "dataSource": "YAHOO", - "date": "1990-01-01T22:00:00.000Z", + "date": "1990-01-01T00:00:00.000Z", "symbol": "MSFT" } ] From 190bb4b6fbaa468ee4d9d1455225a9175bea68ca Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Thu, 7 Nov 2024 19:57:37 +0100 Subject: [PATCH 22/65] Feature/refactor data gathering items (#4032) * Refactoring --- .../src/app/portfolio/current-rate.service.ts | 21 ++++----- .../src/app/portfolio/portfolio.service.ts | 16 ++++--- apps/api/src/app/symbol/symbol.service.ts | 2 +- .../data-provider/data-provider.service.ts | 47 +++++++++++-------- .../data-gathering.processor.ts | 2 +- .../data-gathering/data-gathering.service.ts | 2 +- 6 files changed, 49 insertions(+), 41 deletions(-) diff --git a/apps/api/src/app/portfolio/current-rate.service.ts b/apps/api/src/app/portfolio/current-rate.service.ts index cd199482..ab7bf2eb 100644 --- a/apps/api/src/app/portfolio/current-rate.service.ts +++ b/apps/api/src/app/portfolio/current-rate.service.ts @@ -52,27 +52,24 @@ export class CurrentRateService { .then((dataResultProvider) => { const result: GetValueObject[] = []; - for (const dataGatheringItem of dataGatheringItems) { - if ( - dataResultProvider?.[dataGatheringItem.symbol]?.dataProviderInfo - ) { + for (const { dataSource, symbol } of dataGatheringItems) { + if (dataResultProvider?.[symbol]?.dataProviderInfo) { dataProviderInfos.push( - dataResultProvider[dataGatheringItem.symbol].dataProviderInfo + dataResultProvider[symbol].dataProviderInfo ); } - if (dataResultProvider?.[dataGatheringItem.symbol]?.marketPrice) { + if (dataResultProvider?.[symbol]?.marketPrice) { result.push({ - dataSource: dataGatheringItem.dataSource, + dataSource, + symbol, date: today, - marketPrice: - dataResultProvider?.[dataGatheringItem.symbol]?.marketPrice, - symbol: dataGatheringItem.symbol + marketPrice: dataResultProvider?.[symbol]?.marketPrice }); } else { quoteErrors.push({ - dataSource: dataGatheringItem.dataSource, - symbol: dataGatheringItem.symbol + dataSource, + symbol }); } } diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index d4018686..15ca227e 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -413,15 +413,16 @@ export class PortfolioService { ); } - const dataGatheringItems = positions.map(({ dataSource, symbol }) => { + const assetProfileIdentifiers = positions.map(({ dataSource, symbol }) => { return { dataSource, symbol }; }); - const symbolProfiles = - await this.symbolProfileService.getSymbolProfiles(dataGatheringItems); + const symbolProfiles = await this.symbolProfileService.getSymbolProfiles( + assetProfileIdentifiers + ); const symbolProfileMap: { [symbol: string]: EnhancedSymbolProfile } = {}; for (const symbolProfile of symbolProfiles) { @@ -848,7 +849,7 @@ export class PortfolioService { if (isEmpty(historicalData)) { try { historicalData = await this.dataProviderService.getHistoricalRaw({ - dataGatheringItems: [ + assetProfileIdentifiers: [ { dataSource: DataSource.YAHOO, symbol: aSymbol } ], from: portfolioStart, @@ -953,7 +954,7 @@ export class PortfolioService { return !quantity.eq(0); }); - const dataGatheringItems = positions.map(({ dataSource, symbol }) => { + const assetProfileIdentifiers = positions.map(({ dataSource, symbol }) => { return { dataSource, symbol @@ -961,7 +962,10 @@ export class PortfolioService { }); const [dataProviderResponses, symbolProfiles] = await Promise.all([ - this.dataProviderService.getQuotes({ user, items: dataGatheringItems }), + this.dataProviderService.getQuotes({ + user, + items: assetProfileIdentifiers + }), this.symbolProfileService.getSymbolProfiles( positions.map(({ dataSource, symbol }) => { return { dataSource, symbol }; diff --git a/apps/api/src/app/symbol/symbol.service.ts b/apps/api/src/app/symbol/symbol.service.ts index ae864e2f..56befb9b 100644 --- a/apps/api/src/app/symbol/symbol.service.ts +++ b/apps/api/src/app/symbol/symbol.service.ts @@ -86,7 +86,7 @@ export class SymbolService { try { historicalData = await this.dataProviderService.getHistoricalRaw({ - dataGatheringItems: [{ dataSource, symbol }], + assetProfileIdentifiers: [{ dataSource, symbol }], from: date, to: date }); diff --git a/apps/api/src/services/data-provider/data-provider.service.ts b/apps/api/src/services/data-provider/data-provider.service.ts index bf23f96c..c8a7422d 100644 --- a/apps/api/src/services/data-provider/data-provider.service.ts +++ b/apps/api/src/services/data-provider/data-provider.service.ts @@ -91,11 +91,11 @@ export class DataProviderService { const promises = []; - for (const [dataSource, dataGatheringItems] of Object.entries( + for (const [dataSource, assetProfileIdentifiers] of Object.entries( itemsGroupedByDataSource )) { - const symbols = dataGatheringItems.map((dataGatheringItem) => { - return dataGatheringItem.symbol; + const symbols = assetProfileIdentifiers.map(({ symbol }) => { + return symbol; }); for (const symbol of symbols) { @@ -242,11 +242,11 @@ export class DataProviderService { } public async getHistoricalRaw({ - dataGatheringItems, + assetProfileIdentifiers, from, to }: { - dataGatheringItems: AssetProfileIdentifier[]; + assetProfileIdentifiers: AssetProfileIdentifier[]; from: Date; to: Date; }): Promise<{ @@ -255,25 +255,32 @@ export class DataProviderService { for (const { currency, rootCurrency } of DERIVED_CURRENCIES) { if ( this.hasCurrency({ - dataGatheringItems, + assetProfileIdentifiers, currency: `${DEFAULT_CURRENCY}${currency}` }) ) { // Skip derived currency - dataGatheringItems = dataGatheringItems.filter(({ symbol }) => { - return symbol !== `${DEFAULT_CURRENCY}${currency}`; - }); + assetProfileIdentifiers = assetProfileIdentifiers.filter( + ({ symbol }) => { + return symbol !== `${DEFAULT_CURRENCY}${currency}`; + } + ); // Add root currency - dataGatheringItems.push({ + assetProfileIdentifiers.push({ dataSource: this.getDataSourceForExchangeRates(), symbol: `${DEFAULT_CURRENCY}${rootCurrency}` }); } } - dataGatheringItems = uniqWith(dataGatheringItems, (obj1, obj2) => { - return obj1.dataSource === obj2.dataSource && obj1.symbol === obj2.symbol; - }); + assetProfileIdentifiers = uniqWith( + assetProfileIdentifiers, + (obj1, obj2) => { + return ( + obj1.dataSource === obj2.dataSource && obj1.symbol === obj2.symbol + ); + } + ); const result: { [symbol: string]: { [date: string]: IDataProviderHistoricalResponse }; @@ -283,7 +290,7 @@ export class DataProviderService { data: { [date: string]: IDataProviderHistoricalResponse }; symbol: string; }>[] = []; - for (const { dataSource, symbol } of dataGatheringItems) { + for (const { dataSource, symbol } of assetProfileIdentifiers) { const dataProvider = this.getDataProvider(dataSource); if (dataProvider.canHandle(symbol)) { if (symbol === `${DEFAULT_CURRENCY}USX`) { @@ -418,7 +425,7 @@ export class DataProviderService { const promises: Promise[] = []; - for (const [dataSource, dataGatheringItems] of Object.entries( + for (const [dataSource, assetProfileIdentifiers] of Object.entries( itemsGroupedByDataSource )) { const dataProvider = this.getDataProvider(DataSource[dataSource]); @@ -431,7 +438,7 @@ export class DataProviderService { continue; } - const symbols = dataGatheringItems + const symbols = assetProfileIdentifiers .filter(({ symbol }) => { return !isDerivedCurrency(getCurrencyFromSymbol(symbol)); }) @@ -634,13 +641,13 @@ export class DataProviderService { } private hasCurrency({ - currency, - dataGatheringItems + assetProfileIdentifiers, + currency }: { + assetProfileIdentifiers: AssetProfileIdentifier[]; currency: string; - dataGatheringItems: AssetProfileIdentifier[]; }) { - return dataGatheringItems.some(({ dataSource, symbol }) => { + return assetProfileIdentifiers.some(({ dataSource, symbol }) => { return ( dataSource === this.getDataSourceForExchangeRates() && symbol === currency diff --git a/apps/api/src/services/queues/data-gathering/data-gathering.processor.ts b/apps/api/src/services/queues/data-gathering/data-gathering.processor.ts index dc8cc599..eedad747 100644 --- a/apps/api/src/services/queues/data-gathering/data-gathering.processor.ts +++ b/apps/api/src/services/queues/data-gathering/data-gathering.processor.ts @@ -89,7 +89,7 @@ export class DataGatheringProcessor { ); const historicalData = await this.dataProviderService.getHistoricalRaw({ - dataGatheringItems: [{ dataSource, symbol }], + assetProfileIdentifiers: [{ dataSource, symbol }], from: currentDate, to: new Date() }); diff --git a/apps/api/src/services/queues/data-gathering/data-gathering.service.ts b/apps/api/src/services/queues/data-gathering/data-gathering.service.ts index 72b8ac71..b79b2a09 100644 --- a/apps/api/src/services/queues/data-gathering/data-gathering.service.ts +++ b/apps/api/src/services/queues/data-gathering/data-gathering.service.ts @@ -122,7 +122,7 @@ export class DataGatheringService { }) { try { const historicalData = await this.dataProviderService.getHistoricalRaw({ - dataGatheringItems: [{ dataSource, symbol }], + assetProfileIdentifiers: [{ dataSource, symbol }], from: date, to: date }); From 8fb484af4d066b913343259ca579d82c72e562d6 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Thu, 7 Nov 2024 20:03:37 +0100 Subject: [PATCH 23/65] Bugfix/disable caching of benchmarks in markets overview if sharing (#4027) * Disable caching of benchmarks if sharing mode * Update changelog --- CHANGELOG.md | 1 + apps/api/src/app/benchmark/benchmark.service.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ae5de6f..2a94eaf1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Improved the exception handling in the user authorization service +- Disabled the caching of the benchmarks in the markets overview if sharing the _Fear & Greed Index_ (market mood) is enabled ## 2.121.1 - 2024-11-02 diff --git a/apps/api/src/app/benchmark/benchmark.service.ts b/apps/api/src/app/benchmark/benchmark.service.ts index 36f19684..a659281d 100644 --- a/apps/api/src/app/benchmark/benchmark.service.ts +++ b/apps/api/src/app/benchmark/benchmark.service.ts @@ -437,7 +437,7 @@ export class BenchmarkService { }; }); - if (storeInCache) { + if (!enableSharing && storeInCache) { const expiration = addHours(new Date(), 2); await this.redisCacheService.set( From 70f2f01f8f78286c96d2abbbc019f21dd33c5b07 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Thu, 7 Nov 2024 20:05:01 +0100 Subject: [PATCH 24/65] Bugfix/fix algebraic sign in label of treemap chart (#4030) * Fix algebraic sign * Update changelog --- CHANGELOG.md | 1 + .../lib/treemap-chart/treemap-chart.component.ts | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a94eaf1..97ceff3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- Fixed an issue with the algebraic sign in the chart of the holdings tab on the home page (experimental) - Improved the exception handling in the user authorization service - Disabled the caching of the benchmarks in the markets overview if sharing the _Fear & Greed Index_ (market mood) is enabled diff --git a/libs/ui/src/lib/treemap-chart/treemap-chart.component.ts b/libs/ui/src/lib/treemap-chart/treemap-chart.component.ts index 1acc2c92..9a8594ad 100644 --- a/libs/ui/src/lib/treemap-chart/treemap-chart.component.ts +++ b/libs/ui/src/lib/treemap-chart/treemap-chart.component.ts @@ -261,12 +261,21 @@ export class GfTreemapChartComponent display: true, font: [{ size: 16 }, { lineHeight: 1.5, size: 14 }], formatter: (ctx) => { - const netPerformancePercentWithCurrencyEffect = - ctx.raw._data.netPerformancePercentWithCurrencyEffect; + // Round to 4 decimal places + let netPerformancePercentWithCurrencyEffect = + Math.round( + ctx.raw._data.netPerformancePercentWithCurrencyEffect * 10000 + ) / 10000; + + if (Math.abs(netPerformancePercentWithCurrencyEffect) === 0) { + netPerformancePercentWithCurrencyEffect = Math.abs( + netPerformancePercentWithCurrencyEffect + ); + } return [ ctx.raw._data.symbol, - `${netPerformancePercentWithCurrencyEffect > 0 ? '+' : ''}${(ctx.raw._data.netPerformancePercentWithCurrencyEffect * 100).toFixed(2)}%` + `${netPerformancePercentWithCurrencyEffect > 0 ? '+' : ''}${(netPerformancePercentWithCurrencyEffect * 100).toFixed(2)}%` ]; }, hoverColor: undefined, From f800650c4d4df511dbdfbb1bf3c03129ecadd9c9 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Thu, 7 Nov 2024 20:12:27 +0100 Subject: [PATCH 25/65] Release 2.122.0 (#4033) --- CHANGELOG.md | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97ceff3f..16dad74a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ 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.122.0 - 2024-11-07 ### Changed diff --git a/package-lock.json b/package-lock.json index 9dfb1f59..5edb87ea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ghostfolio", - "version": "2.121.1", + "version": "2.122.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ghostfolio", - "version": "2.121.1", + "version": "2.122.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/package.json b/package.json index 06bee046..8aa893ed 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ghostfolio", - "version": "2.121.1", + "version": "2.122.0", "homepage": "https://ghostfol.io", "license": "AGPL-3.0", "repository": "https://github.com/ghostfolio/ghostfolio", From 9f72835d583dadaf4df626125198b0744e56ba26 Mon Sep 17 00:00:00 2001 From: Gianluca755 <19376177+Gianluca755@users.noreply.github.com> Date: Sat, 9 Nov 2024 20:20:13 +0100 Subject: [PATCH 26/65] Feature/improve language localization for it 20241109 (#4036) * Update translations * Update changelog --- CHANGELOG.md | 1 + apps/client/src/locales/messages.it.xlf | 108 ++++++++++++------------ 2 files changed, 55 insertions(+), 54 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16dad74a..8131113f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Improved the language localization for Italian (`it`) - Upgraded `countries-list` from version `3.1.0` to `3.1.1` ### Fixed diff --git a/apps/client/src/locales/messages.it.xlf b/apps/client/src/locales/messages.it.xlf index 9364d835..8f55b327 100644 --- a/apps/client/src/locales/messages.it.xlf +++ b/apps/client/src/locales/messages.it.xlf @@ -2872,7 +2872,7 @@ Hello, has shared a Portfolio with you! - Salve, ha condiviso un Portafoglio con te! + Salve, ha condiviso un Portafoglio con te! apps/client/src/app/pages/public/public-page.html 4 @@ -5881,7 +5881,7 @@ Currency Cluster Risks - Currency Cluster Risks + Rischio di Concentrazione Valutario apps/client/src/app/pages/portfolio/fire/fire-page.html 141 @@ -5889,7 +5889,7 @@ Account Cluster Risks - Account Cluster Risks + Rischi di Concentrazione dei Conti apps/client/src/app/pages/portfolio/fire/fire-page.html 160 @@ -6237,7 +6237,7 @@ Restricted view - Restricted view + Vista limitata apps/client/src/app/components/access-table/access-table.component.html 26 @@ -6357,7 +6357,7 @@ WTD - WTD + Settimana corrente libs/ui/src/lib/assistant/assistant.component.ts 212 @@ -6373,7 +6373,7 @@ MTD - MTD + Mese corrente libs/ui/src/lib/assistant/assistant.component.ts 216 @@ -6597,7 +6597,7 @@ {VAR_PLURAL, plural, =1 {activity} other {activities}} - {VAR_PLURAL, plural, =1 {activity} other {activities}} + {VAR_PLURAL, plural, =1 {attività} other {attività}} apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html 14 @@ -6781,7 +6781,7 @@ Family Office - Family Office + Ufficio familiare apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts 87 @@ -6837,7 +6837,7 @@ User Experience - User Experience + Esperienza Utente apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts 98 @@ -7061,7 +7061,7 @@ Copy link to clipboard - Copy link to clipboard + Copia link negli appunti apps/client/src/app/components/access-table/access-table.component.html 70 @@ -7069,7 +7069,7 @@ Portfolio Snapshot - Portfolio Snapshot + Stato del Portfolio apps/client/src/app/components/admin-jobs/admin-jobs.html 39 @@ -7077,7 +7077,7 @@ Change with currency effect Change - Change with currency effect Change + Cambio con effetto valuta Cambia apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html 50 @@ -7085,7 +7085,7 @@ Performance with currency effect Performance - Performance with currency effect Performance + Prestazioni con effetto valuta Prestazioni apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html 69 @@ -7093,7 +7093,7 @@ Threshold Min - Threshold Min + Soglia Minima apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html 9 @@ -7101,7 +7101,7 @@ Threshold Max - Threshold Max + Soglia Massima apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html 44 @@ -7109,7 +7109,7 @@ Close - Close + Chiudi apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html 77 @@ -7117,7 +7117,7 @@ Customize - Customize + Personalizza apps/client/src/app/components/rule/rule.component.html 67 @@ -7125,7 +7125,7 @@ No auto-renewal. - No auto-renewal. + No rinnovo automatico. apps/client/src/app/components/user-account-membership/user-account-membership.html 62 @@ -7133,7 +7133,7 @@ Today - Today + Oggi apps/client/src/app/pages/public/public-page.html 24 @@ -7141,7 +7141,7 @@ This year - This year + Anno corrente apps/client/src/app/pages/public/public-page.html 42 @@ -7149,7 +7149,7 @@ From the beginning - From the beginning + Dall'inizio apps/client/src/app/pages/public/public-page.html 60 @@ -7157,7 +7157,7 @@ Oops! Invalid currency. - Oops! Invalid currency. + Oops! Valuta sbagliata. apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.html 49 @@ -7165,7 +7165,7 @@ This page has been archived. - This page has been archived. + Questa pagina è stata archiviata. apps/client/src/app/pages/resources/personal-finance-tools/product-page.html 14 @@ -7173,7 +7173,7 @@ is Open Source Software - is Open Source Software + è un programma Open Source apps/client/src/app/pages/resources/personal-finance-tools/product-page.html 139 @@ -7181,7 +7181,7 @@ is not Open Source Software - is not Open Source Software + non è un programma Open Source apps/client/src/app/pages/resources/personal-finance-tools/product-page.html 146 @@ -7189,7 +7189,7 @@ is Open Source Software - is Open Source Software + è un programma Open Source apps/client/src/app/pages/resources/personal-finance-tools/product-page.html 156 @@ -7197,7 +7197,7 @@ is not Open Source Software - is not Open Source Software + non è un programma Open Source apps/client/src/app/pages/resources/personal-finance-tools/product-page.html 163 @@ -7205,7 +7205,7 @@ can be self-hosted - can be self-hosted + può essere ospitato in proprio apps/client/src/app/pages/resources/personal-finance-tools/product-page.html 178 @@ -7213,7 +7213,7 @@ cannot be self-hosted - cannot be self-hosted + non può essere ospitato in proprio apps/client/src/app/pages/resources/personal-finance-tools/product-page.html 185 @@ -7221,7 +7221,7 @@ can be self-hosted - can be self-hosted + può essere ospitato in proprio apps/client/src/app/pages/resources/personal-finance-tools/product-page.html 195 @@ -7229,7 +7229,7 @@ cannot be self-hosted - cannot be self-hosted + non può essere ospitato in proprio apps/client/src/app/pages/resources/personal-finance-tools/product-page.html 202 @@ -7237,7 +7237,7 @@ can be used anonymously - can be used anonymously + può essere usato anonimamente apps/client/src/app/pages/resources/personal-finance-tools/product-page.html 217 @@ -7245,7 +7245,7 @@ cannot be used anonymously - cannot be used anonymously + non può essere usato anonimamente apps/client/src/app/pages/resources/personal-finance-tools/product-page.html 224 @@ -7253,7 +7253,7 @@ can be used anonymously - can be used anonymously + può essere usato anonimamente apps/client/src/app/pages/resources/personal-finance-tools/product-page.html 234 @@ -7261,7 +7261,7 @@ cannot be used anonymously - cannot be used anonymously + non può essere usato anonimamente apps/client/src/app/pages/resources/personal-finance-tools/product-page.html 241 @@ -7269,7 +7269,7 @@ offers a free plan - offers a free plan + ha un piano gratuito apps/client/src/app/pages/resources/personal-finance-tools/product-page.html 256 @@ -7277,7 +7277,7 @@ does not offer a free plan - does not offer a free plan + non ha un piano gratuito apps/client/src/app/pages/resources/personal-finance-tools/product-page.html 263 @@ -7285,7 +7285,7 @@ offers a free plan - offers a free plan + ha un piano gratuito apps/client/src/app/pages/resources/personal-finance-tools/product-page.html 273 @@ -7293,7 +7293,7 @@ does not offer a free plan - does not offer a free plan + non ha un piano gratuito apps/client/src/app/pages/resources/personal-finance-tools/product-page.html 280 @@ -7301,7 +7301,7 @@ Oops! Could not find any assets. - Oops! Could not find any assets. + Oops! Non ho trovato alcun asset. libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html 37 @@ -7309,7 +7309,7 @@ Data Providers - Data Providers + Fornitori di dati apps/client/src/app/components/admin-settings/admin-settings.component.html 4 @@ -7317,7 +7317,7 @@ NEW - NEW + NUOVO apps/client/src/app/components/admin-settings/admin-settings.component.html 14 @@ -7325,7 +7325,7 @@ Set API Key - Set API Key + Imposta API Key apps/client/src/app/components/admin-settings/admin-settings.component.html 29 @@ -7333,7 +7333,7 @@ Want to stay updated? Click below to get notified as soon as it’s available. - Want to stay updated? Click below to get notified as soon as it’s available. + Vuoi seguire le novità? Clicca sotto per essere notificato appena è disponibile. apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html 23 @@ -7341,7 +7341,7 @@ Notify me - Notify me + Notificami apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html 32 @@ -7349,7 +7349,7 @@ Get access to 100’000+ tickers from over 50 exchanges - Get access to 100’000+ tickers from over 50 exchanges + Ottieni accesso a oltre 100’000+ titoli da oltre 50 borse libs/ui/src/lib/i18n.ts 24 @@ -7357,7 +7357,7 @@ Ukraine - Ukraine + Ucraina libs/ui/src/lib/i18n.ts 92 @@ -7365,7 +7365,7 @@ Skip - Skip + Salta apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html 83 @@ -7373,7 +7373,7 @@ Join now - Join now + Iscriviti adesso apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html 93 @@ -7381,7 +7381,7 @@ Allocation Cluster Risks - Allocation Cluster Risks + Rischi di allocazione dei Conti apps/client/src/app/pages/portfolio/fire/fire-page.html 179 @@ -7389,7 +7389,7 @@ Glossary - Glossary + Glossario apps/client/src/app/pages/resources/glossary/resources-glossary-routing.module.ts 10 @@ -7401,7 +7401,7 @@ Guides - Guides + Guide apps/client/src/app/pages/resources/guides/resources-guides-routing.module.ts 10 @@ -7413,7 +7413,7 @@ guides - guides + guide snake-case apps/client/src/app/pages/resources/overview/resources-overview.component.ts @@ -7426,7 +7426,7 @@ glossary - glossary + glossario snake-case apps/client/src/app/pages/resources/overview/resources-overview.component.ts From 6057794eb6649f0542e9261418df5e478e5b5109 Mon Sep 17 00:00:00 2001 From: Amandee Ellawala <47607256+amandee27@users.noreply.github.com> Date: Sun, 10 Nov 2024 09:29:43 +0000 Subject: [PATCH 27/65] Feature/extend assistant by holding selector (#4031) * Extend assistant by holding selector * Update changelog --- CHANGELOG.md | 6 ++ .../src/app/portfolio/portfolio.controller.ts | 25 +++++ .../src/app/user/update-user-setting.dto.ts | 8 ++ .../app/components/header/header.component.ts | 14 +-- apps/client/src/app/services/data.service.ts | 2 +- .../src/app/services/user/user.service.ts | 14 +++ .../lib/interfaces/user-settings.interface.ts | 2 + .../src/lib/assistant/assistant.component.ts | 94 ++++++++++++++++--- libs/ui/src/lib/assistant/assistant.html | 28 ++++++ 9 files changed, 172 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8131113f..9a9aacf3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ 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 + +### Changed + +- Extended the assistant by a holding selector + ## 2.122.0 - 2024-11-07 ### Changed diff --git a/apps/api/src/app/portfolio/portfolio.controller.ts b/apps/api/src/app/portfolio/portfolio.controller.ts index 326dda15..f2415dff 100644 --- a/apps/api/src/app/portfolio/portfolio.controller.ts +++ b/apps/api/src/app/portfolio/portfolio.controller.ts @@ -74,12 +74,15 @@ export class PortfolioController { @Get('details') @UseGuards(AuthGuard('jwt'), HasPermissionGuard) @UseInterceptors(RedactValuesInResponseInterceptor) + @UseInterceptors(TransformDataSourceInRequestInterceptor) @UseInterceptors(TransformDataSourceInResponseInterceptor) public async getDetails( @Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string, @Query('accounts') filterByAccounts?: string, @Query('assetClasses') filterByAssetClasses?: string, + @Query('dataSource') filterByDataSource?: string, @Query('range') dateRange: DateRange = 'max', + @Query('symbol') filterBySymbol?: string, @Query('tags') filterByTags?: string, @Query('withMarkets') withMarketsParam = 'false' ): Promise { @@ -95,6 +98,8 @@ export class PortfolioController { const filters = this.apiService.buildFiltersFromQueryParams({ filterByAccounts, filterByAssetClasses, + filterByDataSource, + filterBySymbol, filterByTags }); @@ -289,17 +294,22 @@ export class PortfolioController { @Get('dividends') @UseGuards(AuthGuard('jwt'), HasPermissionGuard) + @UseInterceptors(TransformDataSourceInRequestInterceptor) public async getDividends( @Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string, @Query('accounts') filterByAccounts?: string, @Query('assetClasses') filterByAssetClasses?: string, + @Query('dataSource') filterByDataSource?: string, @Query('groupBy') groupBy?: GroupBy, @Query('range') dateRange: DateRange = 'max', + @Query('symbol') filterBySymbol?: string, @Query('tags') filterByTags?: string ): Promise { const filters = this.apiService.buildFiltersFromQueryParams({ filterByAccounts, filterByAssetClasses, + filterByDataSource, + filterBySymbol, filterByTags }); @@ -356,21 +366,26 @@ export class PortfolioController { @Get('holdings') @UseGuards(AuthGuard('jwt'), HasPermissionGuard) @UseInterceptors(RedactValuesInResponseInterceptor) + @UseInterceptors(TransformDataSourceInRequestInterceptor) @UseInterceptors(TransformDataSourceInResponseInterceptor) public async getHoldings( @Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string, @Query('accounts') filterByAccounts?: string, @Query('assetClasses') filterByAssetClasses?: string, + @Query('dataSource') filterByDataSource?: string, @Query('holdingType') filterByHoldingType?: string, @Query('query') filterBySearchQuery?: string, @Query('range') dateRange: DateRange = 'max', + @Query('symbol') filterBySymbol?: string, @Query('tags') filterByTags?: string ): Promise { const filters = this.apiService.buildFiltersFromQueryParams({ filterByAccounts, filterByAssetClasses, + filterByDataSource, filterByHoldingType, filterBySearchQuery, + filterBySymbol, filterByTags }); @@ -386,17 +401,22 @@ export class PortfolioController { @Get('investments') @UseGuards(AuthGuard('jwt'), HasPermissionGuard) + @UseInterceptors(TransformDataSourceInRequestInterceptor) public async getInvestments( @Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string, @Query('accounts') filterByAccounts?: string, @Query('assetClasses') filterByAssetClasses?: string, + @Query('dataSource') filterByDataSource?: string, @Query('groupBy') groupBy?: GroupBy, @Query('range') dateRange: DateRange = 'max', + @Query('symbol') filterBySymbol?: string, @Query('tags') filterByTags?: string ): Promise { const filters = this.apiService.buildFiltersFromQueryParams({ filterByAccounts, filterByAssetClasses, + filterByDataSource, + filterBySymbol, filterByTags }); @@ -451,13 +471,16 @@ export class PortfolioController { @Get('performance') @UseGuards(AuthGuard('jwt'), HasPermissionGuard) @UseInterceptors(PerformanceLoggingInterceptor) + @UseInterceptors(TransformDataSourceInRequestInterceptor) @UseInterceptors(TransformDataSourceInResponseInterceptor) @Version('2') public async getPerformanceV2( @Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string, @Query('accounts') filterByAccounts?: string, @Query('assetClasses') filterByAssetClasses?: string, + @Query('dataSource') filterByDataSource?: string, @Query('range') dateRange: DateRange = 'max', + @Query('symbol') filterBySymbol?: string, @Query('tags') filterByTags?: string, @Query('withExcludedAccounts') withExcludedAccountsParam = 'false' ): Promise { @@ -466,6 +489,8 @@ export class PortfolioController { const filters = this.apiService.buildFiltersFromQueryParams({ filterByAccounts, filterByAssetClasses, + filterByDataSource, + filterBySymbol, filterByTags }); diff --git a/apps/api/src/app/user/update-user-setting.dto.ts b/apps/api/src/app/user/update-user-setting.dto.ts index 13a3a5d2..b34b6fae 100644 --- a/apps/api/src/app/user/update-user-setting.dto.ts +++ b/apps/api/src/app/user/update-user-setting.dto.ts @@ -64,6 +64,14 @@ export class UpdateUserSettingDto { @IsOptional() 'filters.assetClasses'?: string[]; + @IsString() + @IsOptional() + 'filters.dataSource'?: string; + + @IsString() + @IsOptional() + 'filters.symbol'?: string; + @IsArray() @IsOptional() 'filters.tags'?: string[]; diff --git a/apps/client/src/app/components/header/header.component.ts b/apps/client/src/app/components/header/header.component.ts index 1739d113..004fa5f3 100644 --- a/apps/client/src/app/components/header/header.component.ts +++ b/apps/client/src/app/components/header/header.component.ts @@ -175,17 +175,17 @@ export class HeaderComponent implements OnChanges { const userSetting: UpdateUserSettingDto = {}; for (const filter of filters) { - let filtersType: string; - if (filter.type === 'ACCOUNT') { - filtersType = 'accounts'; + userSetting['filters.accounts'] = filter.id ? [filter.id] : null; } else if (filter.type === 'ASSET_CLASS') { - filtersType = 'assetClasses'; + userSetting['filters.assetClasses'] = filter.id ? [filter.id] : null; + } else if (filter.type === 'DATA_SOURCE') { + userSetting['filters.dataSource'] = filter.id ? filter.id : null; + } else if (filter.type === 'SYMBOL') { + userSetting['filters.symbol'] = filter.id ? filter.id : null; } else if (filter.type === 'TAG') { - filtersType = 'tags'; + userSetting['filters.tags'] = filter.id ? [filter.id] : null; } - - userSetting[`filters.${filtersType}`] = filter.id ? [filter.id] : null; } this.dataService diff --git a/apps/client/src/app/services/data.service.ts b/apps/client/src/app/services/data.service.ts index cde7555b..dccbb064 100644 --- a/apps/client/src/app/services/data.service.ts +++ b/apps/client/src/app/services/data.service.ts @@ -532,7 +532,7 @@ export class DataService { }: { filters?: Filter[]; range?: DateRange; - }) { + } = {}) { let params = this.buildFiltersAsQueryParams({ filters }); if (range) { diff --git a/apps/client/src/app/services/user/user.service.ts b/apps/client/src/app/services/user/user.service.ts index 3ecc58c1..aa91a90b 100644 --- a/apps/client/src/app/services/user/user.service.ts +++ b/apps/client/src/app/services/user/user.service.ts @@ -65,6 +65,20 @@ export class UserService extends ObservableStore { }); } + if (user?.settings['filters.dataSource']) { + filters.push({ + id: user.settings['filters.dataSource'], + type: 'DATA_SOURCE' + }); + } + + if (user?.settings['filters.symbol']) { + filters.push({ + id: user.settings['filters.symbol'], + type: 'SYMBOL' + }); + } + if (user?.settings['filters.tags']) { filters.push({ id: user.settings['filters.tags'][0], diff --git a/libs/common/src/lib/interfaces/user-settings.interface.ts b/libs/common/src/lib/interfaces/user-settings.interface.ts index e9e90e71..d72be7c7 100644 --- a/libs/common/src/lib/interfaces/user-settings.interface.ts +++ b/libs/common/src/lib/interfaces/user-settings.interface.ts @@ -14,6 +14,8 @@ export interface UserSettings { dateRange?: DateRange; emergencyFund?: number; 'filters.accounts'?: string[]; + 'filters.dataSource'?: string; + 'filters.symbol'?: string; 'filters.tags'?: string[]; holdingsViewMode?: HoldingsViewMode; isExperimentalFeatures?: boolean; diff --git a/libs/ui/src/lib/assistant/assistant.component.ts b/libs/ui/src/lib/assistant/assistant.component.ts index d73cdb41..3a5e6a2f 100644 --- a/libs/ui/src/lib/assistant/assistant.component.ts +++ b/libs/ui/src/lib/assistant/assistant.component.ts @@ -1,7 +1,9 @@ import { GfAssetProfileIconComponent } from '@ghostfolio/client/components/asset-profile-icon/asset-profile-icon.component'; +import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module'; import { AdminService } from '@ghostfolio/client/services/admin.service'; import { DataService } from '@ghostfolio/client/services/data.service'; -import { Filter, User } from '@ghostfolio/common/interfaces'; +import { getAssetProfileIdentifier } from '@ghostfolio/common/helper'; +import { Filter, PortfolioPosition, User } from '@ghostfolio/common/interfaces'; import { DateRange } from '@ghostfolio/common/types'; import { translate } from '@ghostfolio/ui/i18n'; @@ -35,7 +37,7 @@ import { MatFormFieldModule } from '@angular/material/form-field'; import { MatMenuTrigger } from '@angular/material/menu'; import { MatSelectModule } from '@angular/material/select'; import { RouterModule } from '@angular/router'; -import { Account, AssetClass } from '@prisma/client'; +import { Account, AssetClass, DataSource } from '@prisma/client'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import { EMPTY, Observable, Subject, lastValueFrom } from 'rxjs'; import { @@ -61,6 +63,7 @@ import { FormsModule, GfAssetProfileIconComponent, GfAssistantListItemComponent, + GfSymbolModule, MatButtonModule, MatFormFieldModule, MatSelectModule, @@ -132,8 +135,10 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { public filterForm = this.formBuilder.group({ account: new FormControl(undefined), assetClass: new FormControl(undefined), + holding: new FormControl(undefined), tag: new FormControl(undefined) }); + public holdings: PortfolioPosition[] = []; public isLoading = false; public isOpen = false; public placeholder = $localize`Find holding...`; @@ -144,7 +149,13 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { }; public tags: Filter[] = []; - private filterTypes: Filter['type'][] = ['ACCOUNT', 'ASSET_CLASS', 'TAG']; + private filterTypes: Filter['type'][] = [ + 'ACCOUNT', + 'ASSET_CLASS', + 'DATA_SOURCE', + 'SYMBOL', + 'TAG' + ]; private keyManager: FocusKeyManager; private unsubscribeSubject = new Subject(); @@ -156,6 +167,8 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { ) {} public ngOnInit() { + this.initializeFilterForm(); + this.assetClasses = Object.keys(AssetClass).map((assetClass) => { return { id: assetClass, @@ -263,16 +276,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { this.filterForm.enable({ emitEvent: false }); } - this.filterForm.setValue( - { - account: this.user?.settings?.['filters.accounts']?.[0] ?? null, - assetClass: this.user?.settings?.['filters.assetClasses']?.[0] ?? null, - tag: this.user?.settings?.['filters.tags']?.[0] ?? null - }, - { - emitEvent: false - } - ); + this.initializeFilterForm(); this.tags = this.user?.tags @@ -298,6 +302,19 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { }); } + public holdingComparisonFunction( + option: PortfolioPosition, + value: PortfolioPosition + ): boolean { + if (value === null) { + return false; + } + + return ( + getAssetProfileIdentifier(option) === getAssetProfileIdentifier(value) + ); + } + public async initialize() { this.isLoading = true; this.keyManager = new FocusKeyManager(this.assistantListItems).withWrap(); @@ -331,6 +348,14 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { id: this.filterForm.get('assetClass').value, type: 'ASSET_CLASS' }, + { + id: this.filterForm.get('holding').value?.dataSource, + type: 'DATA_SOURCE' + }, + { + id: this.filterForm.get('holding').value?.symbol, + type: 'SYMBOL' + }, { id: this.filterForm.get('tag').value, type: 'TAG' @@ -473,4 +498,47 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { takeUntil(this.unsubscribeSubject) ); } + + private initializeFilterForm() { + this.dataService + .fetchPortfolioHoldings() + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe(({ holdings }) => { + this.holdings = holdings + .filter(({ assetSubClass }) => { + return !['CASH'].includes(assetSubClass); + }) + .sort((a, b) => { + return a.name?.localeCompare(b.name); + }); + this.setFilterFormValues(); + }); + } + + private setFilterFormValues() { + const dataSource = this.user?.settings?.[ + 'filters.dataSource' + ] as DataSource; + const symbol = this.user?.settings?.['filters.symbol']; + const selectedHolding = this.holdings.find((holding) => { + return ( + getAssetProfileIdentifier({ + dataSource: holding.dataSource, + symbol: holding.symbol + }) === getAssetProfileIdentifier({ dataSource, symbol }) + ); + }); + + this.filterForm.setValue( + { + account: this.user?.settings?.['filters.accounts']?.[0] ?? null, + assetClass: this.user?.settings?.['filters.assetClasses']?.[0] ?? null, + holding: selectedHolding ?? null, + tag: this.user?.settings?.['filters.tags']?.[0] ?? null + }, + { + emitEvent: false + } + ); + } } diff --git a/libs/ui/src/lib/assistant/assistant.html b/libs/ui/src/lib/assistant/assistant.html index 648c791a..18c2145a 100644 --- a/libs/ui/src/lib/assistant/assistant.html +++ b/libs/ui/src/lib/assistant/assistant.html @@ -122,6 +122,34 @@ +
    + + Holding + + {{ + filterForm.get('holding')?.value?.name + }} + + @for (holding of holdings; track holding.name) { + +
    + {{ holding.name }} +
    + {{ holding.symbol | gfSymbol }} · + {{ holding.currency }} +
    +
    + } +
    +
    +
    Tags From 09a9148fecf8067e3bada5201a45f86900fe3b77 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 10 Nov 2024 11:01:39 +0100 Subject: [PATCH 28/65] Bugfix/move changelog entry (#4038) * Move changelog entry --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a9aacf3..5bb45d74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,12 +10,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Extended the assistant by a holding selector +- Improved the language localization for Italian (`it`) ## 2.122.0 - 2024-11-07 ### Changed -- Improved the language localization for Italian (`it`) - Upgraded `countries-list` from version `3.1.0` to `3.1.1` ### Fixed From 15856264f8522fb4680cdd36d3bbbbb7fec00066 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 11 Nov 2024 19:26:43 +0100 Subject: [PATCH 29/65] Feature/upgrade ngx-skeleton-loader to version 9.0.0 (#4026) * Upgrade ngx-skeleton-loader to version 9.0.0 * Update changelog --- CHANGELOG.md | 1 + package-lock.json | 25 ++++++------------------- package.json | 2 +- 3 files changed, 8 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5bb45d74..2700af21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Extended the assistant by a holding selector - Improved the language localization for Italian (`it`) +- Upgraded `ngx-skeleton-loader` from version `7.0.0` to `9.0.0` ## 2.122.0 - 2024-11-07 diff --git a/package-lock.json b/package-lock.json index 5edb87ea..fff9ff65 100644 --- a/package-lock.json +++ b/package-lock.json @@ -77,7 +77,7 @@ "ng-extract-i18n-merge": "2.12.0", "ngx-device-detector": "8.0.0", "ngx-markdown": "18.0.0", - "ngx-skeleton-loader": "7.0.0", + "ngx-skeleton-loader": "9.0.0", "ngx-stripe": "18.1.0", "open-color": "1.9.1", "papaparse": "5.3.1", @@ -26858,17 +26858,16 @@ } }, "node_modules/ngx-skeleton-loader": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/ngx-skeleton-loader/-/ngx-skeleton-loader-7.0.0.tgz", - "integrity": "sha512-myc6GNcNhyksZrimIFkCxeihi0kQ8JhQVZiGbtiIv4gYrnnRk5nXbs3kYitK8E8OstHG+jlsmRofqGBxuIsYTA==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/ngx-skeleton-loader/-/ngx-skeleton-loader-9.0.0.tgz", + "integrity": "sha512-aO4/V6oGdZGNcTjasTg/fwzJJYl/ZmNKgCukOEQdUK3GSFOZtB/3GGULMJuZ939hk3Hzqh1OBiLfIM1SqTfhqg==", "license": "MIT", "dependencies": { - "perf-marks": "^1.13.4", "tslib": "^2.0.0" }, "peerDependencies": { - "@angular/common": ">=8.0.0", - "@angular/core": ">=8.0.0" + "@angular/common": ">=16.0.0", + "@angular/core": ">=16.0.0" } }, "node_modules/ngx-stripe": { @@ -28423,18 +28422,6 @@ "dev": true, "license": "MIT" }, - "node_modules/perf-marks": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/perf-marks/-/perf-marks-1.14.2.tgz", - "integrity": "sha512-N0/bQcuTlETpgox/DsXS1voGjqaoamMoiyhncgeW3rSHy/qw8URVgmPRYfFDQns/+C6yFUHDbeSBGL7ixT6Y4A==", - "license": "MIT", - "dependencies": { - "tslib": "^2.1.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", diff --git a/package.json b/package.json index 8aa893ed..ca24d28b 100644 --- a/package.json +++ b/package.json @@ -123,7 +123,7 @@ "ng-extract-i18n-merge": "2.12.0", "ngx-device-detector": "8.0.0", "ngx-markdown": "18.0.0", - "ngx-skeleton-loader": "7.0.0", + "ngx-skeleton-loader": "9.0.0", "ngx-stripe": "18.1.0", "open-color": "1.9.1", "papaparse": "5.3.1", From 92b025bff3a7f770d920cb58fbe5bbc662ee5ac2 Mon Sep 17 00:00:00 2001 From: Mohan <41165473+mohanbyte@users.noreply.github.com> Date: Tue, 12 Nov 2024 01:37:02 +0530 Subject: [PATCH 30/65] Feature/separate FIRE and X-ray pages (#4037) * Separate FIRE / X-ray page * Update changelog --- CHANGELOG.md | 1 + .../fire/fire-page-routing.module.ts | 2 +- .../portfolio/fire/fire-page.component.ts | 93 +---------- .../app/pages/portfolio/fire/fire-page.html | 130 --------------- .../pages/portfolio/fire/fire-page.module.ts | 2 - .../portfolio-page-routing.module.ts | 5 + .../portfolio/portfolio-page.component.ts | 7 +- .../x-ray/x-ray-page-routing.module.ts | 21 +++ .../portfolio/x-ray/x-ray-page.component.html | 123 ++++++++++++++ .../portfolio/x-ray/x-ray-page.component.scss | 3 + .../portfolio/x-ray/x-ray-page.component.ts | 150 ++++++++++++++++++ .../portfolio/x-ray/x-ray-page.module.ts | 22 +++ 12 files changed, 333 insertions(+), 226 deletions(-) create mode 100644 apps/client/src/app/pages/portfolio/x-ray/x-ray-page-routing.module.ts create mode 100644 apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html create mode 100644 apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.scss create mode 100644 apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.ts create mode 100644 apps/client/src/app/pages/portfolio/x-ray/x-ray-page.module.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 2700af21..8f3628ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Extended the assistant by a holding selector +- Separated the _FIRE_ / _X-ray_ page - Improved the language localization for Italian (`it`) - Upgraded `ngx-skeleton-loader` from version `7.0.0` to `9.0.0` diff --git a/apps/client/src/app/pages/portfolio/fire/fire-page-routing.module.ts b/apps/client/src/app/pages/portfolio/fire/fire-page-routing.module.ts index 885dc550..96260add 100644 --- a/apps/client/src/app/pages/portfolio/fire/fire-page-routing.module.ts +++ b/apps/client/src/app/pages/portfolio/fire/fire-page-routing.module.ts @@ -10,7 +10,7 @@ const routes: Routes = [ canActivate: [AuthGuard], component: FirePageComponent, path: '', - title: $localize`FIRE` + title: 'FIRE' } ]; diff --git a/apps/client/src/app/pages/portfolio/fire/fire-page.component.ts b/apps/client/src/app/pages/portfolio/fire/fire-page.component.ts index d20c6691..897b9824 100644 --- a/apps/client/src/app/pages/portfolio/fire/fire-page.component.ts +++ b/apps/client/src/app/pages/portfolio/fire/fire-page.component.ts @@ -1,12 +1,7 @@ -import { UpdateUserSettingDto } from '@ghostfolio/api/app/user/update-user-setting.dto'; 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 { - PortfolioReport, - PortfolioReportRule, - User -} from '@ghostfolio/common/interfaces'; +import { User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; @@ -21,18 +16,11 @@ import { takeUntil } from 'rxjs/operators'; templateUrl: './fire-page.html' }) export class FirePageComponent implements OnDestroy, OnInit { - public accountClusterRiskRules: PortfolioReportRule[]; - public currencyClusterRiskRules: PortfolioReportRule[]; public deviceType: string; - public economicMarketClusterRiskRules: PortfolioReportRule[]; - public emergencyFundRules: PortfolioReportRule[]; - public feeRules: PortfolioReportRule[]; public fireWealth: Big; public hasImpersonationId: boolean; public hasPermissionToUpdateUserSettings: boolean; - public inactiveRules: PortfolioReportRule[]; public isLoading = false; - public isLoadingPortfolioReport = false; public user: User; public withdrawalRatePerMonth: Big; public withdrawalRatePerYear: Big; @@ -95,8 +83,6 @@ export class FirePageComponent implements OnDestroy, OnInit { this.changeDetectorRef.markForCheck(); } }); - - this.initializePortfolioReport(); } public onAnnualInterestRateChange(annualInterestRate: number) { @@ -133,21 +119,6 @@ export class FirePageComponent implements OnDestroy, OnInit { }); }); } - - public onRulesUpdated(event: UpdateUserSettingDto) { - this.dataService - .putUserSetting(event) - .pipe(takeUntil(this.unsubscribeSubject)) - .subscribe(() => { - this.userService - .get(true) - .pipe(takeUntil(this.unsubscribeSubject)) - .subscribe(); - - this.initializePortfolioReport(); - }); - } - public onSavingsRateChange(savingsRate: number) { this.dataService .putUserSetting({ savingsRate }) @@ -187,66 +158,4 @@ export class FirePageComponent implements OnDestroy, OnInit { this.unsubscribeSubject.next(); this.unsubscribeSubject.complete(); } - - private initializePortfolioReport() { - this.isLoadingPortfolioReport = true; - - this.dataService - .fetchPortfolioReport() - .pipe(takeUntil(this.unsubscribeSubject)) - .subscribe((portfolioReport) => { - this.inactiveRules = this.mergeInactiveRules(portfolioReport); - - this.accountClusterRiskRules = - portfolioReport.rules['accountClusterRisk']?.filter( - ({ isActive }) => { - return isActive; - } - ) ?? null; - - this.currencyClusterRiskRules = - portfolioReport.rules['currencyClusterRisk']?.filter( - ({ isActive }) => { - return isActive; - } - ) ?? null; - - this.economicMarketClusterRiskRules = - portfolioReport.rules['economicMarketClusterRisk']?.filter( - ({ isActive }) => { - return isActive; - } - ) ?? null; - - this.emergencyFundRules = - portfolioReport.rules['emergencyFund']?.filter(({ isActive }) => { - return isActive; - }) ?? null; - - this.feeRules = - portfolioReport.rules['fees']?.filter(({ isActive }) => { - return isActive; - }) ?? null; - - this.isLoadingPortfolioReport = false; - - this.changeDetectorRef.markForCheck(); - }); - } - - private mergeInactiveRules(report: PortfolioReport): PortfolioReportRule[] { - let inactiveRules: PortfolioReportRule[] = []; - - for (const category in report.rules) { - const rulesArray = report.rules[category]; - - inactiveRules = inactiveRules.concat( - rulesArray.filter(({ isActive }) => { - return !isActive; - }) - ); - } - - return inactiveRules; - } } diff --git a/apps/client/src/app/pages/portfolio/fire/fire-page.html b/apps/client/src/app/pages/portfolio/fire/fire-page.html index 7a336b62..77fd1640 100644 --- a/apps/client/src/app/pages/portfolio/fire/fire-page.html +++ b/apps/client/src/app/pages/portfolio/fire/fire-page.html @@ -101,133 +101,3 @@ }
    - -
    -
    -
    -

    X-ray

    -

    - Ghostfolio X-ray uses static analysis to identify potential issues - and risks in your portfolio. - It will be highly configurable in the future: activate / deactivate - rules and customize the thresholds to match your personal investment - style. -

    -
    -

    - Emergency Fund - @if (user?.subscription?.type === 'Basic') { - - } -

    - -
    -
    -

    - Currency Cluster Risks - @if (user?.subscription?.type === 'Basic') { - - } -

    - -
    -
    -

    - Account Cluster Risks - @if (user?.subscription?.type === 'Basic') { - - } -

    - -
    -
    -

    - Economic Market Cluster Risks - @if (user?.subscription?.type === 'Basic') { - - } -

    - -
    -
    -

    - Fees - @if (user?.subscription?.type === 'Basic') { - - } -

    - -
    - @if (inactiveRules?.length > 0) { -
    -

    Inactive

    - -
    - } -
    -
    -
    diff --git a/apps/client/src/app/pages/portfolio/fire/fire-page.module.ts b/apps/client/src/app/pages/portfolio/fire/fire-page.module.ts index 60e3127d..a606ae1b 100644 --- a/apps/client/src/app/pages/portfolio/fire/fire-page.module.ts +++ b/apps/client/src/app/pages/portfolio/fire/fire-page.module.ts @@ -1,4 +1,3 @@ -import { GfRulesModule } from '@ghostfolio/client/components/rules/rules.module'; import { GfFireCalculatorComponent } from '@ghostfolio/ui/fire-calculator'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; import { GfValueComponent } from '@ghostfolio/ui/value'; @@ -17,7 +16,6 @@ import { FirePageComponent } from './fire-page.component'; FirePageRoutingModule, GfFireCalculatorComponent, GfPremiumIndicatorComponent, - GfRulesModule, GfValueComponent, NgxSkeletonLoaderModule ], diff --git a/apps/client/src/app/pages/portfolio/portfolio-page-routing.module.ts b/apps/client/src/app/pages/portfolio/portfolio-page-routing.module.ts index 6146c573..20de6f8f 100644 --- a/apps/client/src/app/pages/portfolio/portfolio-page-routing.module.ts +++ b/apps/client/src/app/pages/portfolio/portfolio-page-routing.module.ts @@ -34,6 +34,11 @@ const routes: Routes = [ path: 'fire', loadChildren: () => import('./fire/fire-page.module').then((m) => m.FirePageModule) + }, + { + path: 'x-ray', + loadChildren: () => + import('./x-ray/x-ray-page.module').then((m) => m.XRayPageModule) } ], component: PortfolioPageComponent, diff --git a/apps/client/src/app/pages/portfolio/portfolio-page.component.ts b/apps/client/src/app/pages/portfolio/portfolio-page.component.ts index 0c980e25..7f40bf1d 100644 --- a/apps/client/src/app/pages/portfolio/portfolio-page.component.ts +++ b/apps/client/src/app/pages/portfolio/portfolio-page.component.ts @@ -46,8 +46,13 @@ export class PortfolioPageComponent implements OnDestroy, OnInit { }, { iconName: 'calculator-outline', - label: 'FIRE / X-ray', + label: 'FIRE ', path: ['/portfolio', 'fire'] + }, + { + iconName: 'scan-outline', + label: 'X-ray', + path: ['/portfolio', 'x-ray'] } ]; this.user = state.user; diff --git a/apps/client/src/app/pages/portfolio/x-ray/x-ray-page-routing.module.ts b/apps/client/src/app/pages/portfolio/x-ray/x-ray-page-routing.module.ts new file mode 100644 index 00000000..091cbc49 --- /dev/null +++ b/apps/client/src/app/pages/portfolio/x-ray/x-ray-page-routing.module.ts @@ -0,0 +1,21 @@ +import { AuthGuard } from '@ghostfolio/client/core/auth.guard'; + +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +import { XRayPageComponent } from './x-ray-page.component'; + +const routes: Routes = [ + { + canActivate: [AuthGuard], + component: XRayPageComponent, + path: '', + title: 'X-ray' + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class XRayPageRoutingModule {} diff --git a/apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html b/apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html new file mode 100644 index 00000000..cd03b49b --- /dev/null +++ b/apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html @@ -0,0 +1,123 @@ +
    +
    +
    +

    X-ray

    +

    + Ghostfolio X-ray uses static analysis to uncover potential issues and + risks in your portfolio. Adjust the rules below and set custom + thresholds to align with your personal investment strategy. +

    +
    +

    + Emergency Fund + @if (user?.subscription?.type === 'Basic') { + + } +

    + +
    +
    +

    + Currency Cluster Risks + @if (user?.subscription?.type === 'Basic') { + + } +

    + +
    +
    +

    + Account Cluster Risks + @if (user?.subscription?.type === 'Basic') { + + } +

    + +
    +
    +

    + Economic Market Cluster Risks + @if (user?.subscription?.type === 'Basic') { + + } +

    + +
    +
    +

    + Fees + @if (user?.subscription?.type === 'Basic') { + + } +

    + +
    + @if (inactiveRules?.length > 0) { +
    +

    Inactive

    + +
    + } +
    +
    +
    diff --git a/apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.scss b/apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.scss new file mode 100644 index 00000000..5d4e87f3 --- /dev/null +++ b/apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.scss @@ -0,0 +1,3 @@ +:host { + display: block; +} diff --git a/apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.ts b/apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.ts new file mode 100644 index 00000000..36f42fc3 --- /dev/null +++ b/apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.ts @@ -0,0 +1,150 @@ +import { UpdateUserSettingDto } from '@ghostfolio/api/app/user/update-user-setting.dto'; +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 { + PortfolioReportRule, + PortfolioReport +} from '@ghostfolio/common/interfaces'; +import { User } from '@ghostfolio/common/interfaces/user.interface'; +import { hasPermission, permissions } from '@ghostfolio/common/permissions'; + +import { ChangeDetectorRef, Component } from '@angular/core'; +import { Subject, takeUntil } from 'rxjs'; + +@Component({ + selector: 'gf-x-ray-page', + styleUrl: './x-ray-page.component.scss', + templateUrl: './x-ray-page.component.html' +}) +export class XRayPageComponent { + public accountClusterRiskRules: PortfolioReportRule[]; + public currencyClusterRiskRules: PortfolioReportRule[]; + public economicMarketClusterRiskRules: PortfolioReportRule[]; + public emergencyFundRules: PortfolioReportRule[]; + public feeRules: PortfolioReportRule[]; + public hasImpersonationId: boolean; + public hasPermissionToUpdateUserSettings: boolean; + public inactiveRules: PortfolioReportRule[]; + public isLoadingPortfolioReport = false; + public user: User; + + private unsubscribeSubject = new Subject(); + + public constructor( + private changeDetectorRef: ChangeDetectorRef, + private dataService: DataService, + private impersonationStorageService: ImpersonationStorageService, + private userService: UserService + ) {} + + public ngOnInit() { + this.impersonationStorageService + .onChangeHasImpersonation() + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe((impersonationId) => { + this.hasImpersonationId = !!impersonationId; + }); + + this.userService.stateChanged + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe((state) => { + if (state?.user) { + this.user = state.user; + + this.hasPermissionToUpdateUserSettings = + this.user.subscription?.type === 'Basic' + ? false + : hasPermission( + this.user.permissions, + permissions.updateUserSettings + ); + + this.changeDetectorRef.markForCheck(); + } + }); + + this.initializePortfolioReport(); + } + + public onRulesUpdated(event: UpdateUserSettingDto) { + this.dataService + .putUserSetting(event) + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe(() => { + this.userService + .get(true) + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe(); + + this.initializePortfolioReport(); + }); + } + + public ngOnDestroy() { + this.unsubscribeSubject.next(); + this.unsubscribeSubject.complete(); + } + + private initializePortfolioReport() { + this.isLoadingPortfolioReport = true; + + this.dataService + .fetchPortfolioReport() + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe((portfolioReport) => { + this.inactiveRules = this.mergeInactiveRules(portfolioReport); + + this.accountClusterRiskRules = + portfolioReport.rules['accountClusterRisk']?.filter( + ({ isActive }) => { + return isActive; + } + ) ?? null; + + this.currencyClusterRiskRules = + portfolioReport.rules['currencyClusterRisk']?.filter( + ({ isActive }) => { + return isActive; + } + ) ?? null; + + this.economicMarketClusterRiskRules = + portfolioReport.rules['economicMarketClusterRisk']?.filter( + ({ isActive }) => { + return isActive; + } + ) ?? null; + + this.emergencyFundRules = + portfolioReport.rules['emergencyFund']?.filter(({ isActive }) => { + return isActive; + }) ?? null; + + this.feeRules = + portfolioReport.rules['fees']?.filter(({ isActive }) => { + return isActive; + }) ?? null; + + this.isLoadingPortfolioReport = false; + + this.changeDetectorRef.markForCheck(); + }); + } + + private mergeInactiveRules(report: PortfolioReport): PortfolioReportRule[] { + let inactiveRules: PortfolioReportRule[] = []; + + for (const category in report.rules) { + const rulesArray = report.rules[category]; + + inactiveRules = inactiveRules.concat( + rulesArray.filter(({ isActive }) => { + return !isActive; + }) + ); + } + + return inactiveRules; + } +} diff --git a/apps/client/src/app/pages/portfolio/x-ray/x-ray-page.module.ts b/apps/client/src/app/pages/portfolio/x-ray/x-ray-page.module.ts new file mode 100644 index 00000000..bff4f4dc --- /dev/null +++ b/apps/client/src/app/pages/portfolio/x-ray/x-ray-page.module.ts @@ -0,0 +1,22 @@ +import { GfRulesModule } from '@ghostfolio/client/components/rules/rules.module'; +import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; + +import { CommonModule } from '@angular/common'; +import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; +import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; + +import { XRayPageRoutingModule } from './x-ray-page-routing.module'; +import { XRayPageComponent } from './x-ray-page.component'; + +@NgModule({ + declarations: [XRayPageComponent], + imports: [ + CommonModule, + GfPremiumIndicatorComponent, + GfRulesModule, + NgxSkeletonLoaderModule, + XRayPageRoutingModule + ], + schemas: [CUSTOM_ELEMENTS_SCHEMA] +}) +export class XRayPageModule {} From 95cb6dcb8d8289facb8ef70e3b851b8cb64f1811 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 13 Nov 2024 14:34:30 +0100 Subject: [PATCH 31/65] Feature/upgrade UUID to version 11.0.2 (#4029) * Upgrade uuid to version 11.0.2 * Update changelog --- CHANGELOG.md | 1 + package-lock.json | 38 +++++++++++++++++++++++++++++++++----- package.json | 2 +- 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f3628ca..44a09a6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Separated the _FIRE_ / _X-ray_ page - Improved the language localization for Italian (`it`) - Upgraded `ngx-skeleton-loader` from version `7.0.0` to `9.0.0` +- Upgraded `uuid` from version `9.0.1` to `11.0.2` ## 2.122.0 - 2024-11-07 diff --git a/package-lock.json b/package-lock.json index fff9ff65..f6351537 100644 --- a/package-lock.json +++ b/package-lock.json @@ -89,7 +89,7 @@ "stripe": "17.3.0", "svgmap": "2.6.0", "twitter-api-v2": "1.14.2", - "uuid": "9.0.1", + "uuid": "11.0.2", "yahoo-finance2": "2.11.3", "zone.js": "0.14.10" }, @@ -8900,6 +8900,20 @@ "storybook": "^8.3.6" } }, + "node_modules/@storybook/addon-actions/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@storybook/addon-backgrounds": { "version": "8.3.6", "resolved": "https://registry.npmjs.org/@storybook/addon-backgrounds/-/addon-backgrounds-8.3.6.tgz", @@ -25771,6 +25785,20 @@ "web-worker": "^1.2.0" } }, + "node_modules/mermaid/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "optional": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -33552,16 +33580,16 @@ } }, "node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.2.tgz", + "integrity": "sha512-14FfcOJmqdjbBPdDjFQyk/SdT4NySW4eM0zcG+HqbHP5jzuH56xO3J1DGhgs/cEMCfwYi3HQI1gnTO62iaG+tQ==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" ], "license": "MIT", "bin": { - "uuid": "dist/bin/uuid" + "uuid": "dist/esm/bin/uuid" } }, "node_modules/uvu": { diff --git a/package.json b/package.json index ca24d28b..9fa4d045 100644 --- a/package.json +++ b/package.json @@ -135,7 +135,7 @@ "stripe": "17.3.0", "svgmap": "2.6.0", "twitter-api-v2": "1.14.2", - "uuid": "9.0.1", + "uuid": "11.0.2", "yahoo-finance2": "2.11.3", "zone.js": "0.14.10" }, From 8c0de59414fe43eb4b65f9941f0cb3872963efcd Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 16 Nov 2024 00:02:28 +0100 Subject: [PATCH 32/65] Feature/move treemap chart from experimental to general availability (#4034) * Move treemap chart to general availability * Update changelog --- CHANGELOG.md | 1 + .../home-holdings/home-holdings.html | 30 +++++++++---------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44a09a6b..db6ab362 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Moved the chart of the holdings tab on the home page from experimental to general availability - Extended the assistant by a holding selector - Separated the _FIRE_ / _X-ray_ page - Improved the language localization for Italian (`it`) diff --git a/apps/client/src/app/components/home-holdings/home-holdings.html b/apps/client/src/app/components/home-holdings/home-holdings.html index f1c4e7e8..abbc93b3 100644 --- a/apps/client/src/app/components/home-holdings/home-holdings.html +++ b/apps/client/src/app/components/home-holdings/home-holdings.html @@ -7,23 +7,21 @@
    - @if (user?.settings?.isExperimentalFeatures) { -
    -
    - - - - - - - - -
    +
    +
    + + + + + + + +
    - } +
    Date: Sat, 16 Nov 2024 11:17:58 +0000 Subject: [PATCH 33/65] Feature/implement range slider in rule settings dialog (#4043) * Implement range slider in rule settings dialog * Update changelog --- CHANGELOG.md | 1 + .../rule-settings-dialog.html | 188 +++++++++++------- .../rule-settings-dialog.scss | 3 + 3 files changed, 124 insertions(+), 68 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index db6ab362..89857f9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Moved the chart of the holdings tab on the home page from experimental to general availability - Extended the assistant by a holding selector - Separated the _FIRE_ / _X-ray_ page +- Improved the usability to customize the rule thresholds in the _X-ray_ page by introducing range sliders (experimental) - Improved the language localization for Italian (`it`) - Upgraded `ngx-skeleton-loader` from version `7.0.0` to `9.0.0` - Upgraded `uuid` from version `9.0.1` to `11.0.2` diff --git a/apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html b/apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html index 8806dae6..97854ad7 100644 --- a/apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html +++ b/apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html @@ -1,76 +1,128 @@
    {{ data.rule.name }}
    -
    -
    - Threshold Min: - @if (data.rule.configuration.threshold.unit === '%') { - {{ data.settings.thresholdMin | percent: '1.2-2' }} - } @else { - {{ data.settings.thresholdMin }} - } -
    - @if (data.rule.configuration.threshold.unit === '%') { - - } @else { - - } - +
    + Threshold range: + @if (data.rule.configuration.threshold.unit === '%') { + {{ data.settings.thresholdMin | percent: '1.2-2' }} + } @else { + {{ data.settings.thresholdMin }} + } + - + @if (data.rule.configuration.threshold.unit === '%') { + {{ data.settings.thresholdMax | percent: '1.2-2' }} + } @else { + {{ data.settings.thresholdMax }} + } +
    +
    + @if (data.rule.configuration.threshold.unit === '%') { + + } @else { + + } + + + + + @if (data.rule.configuration.threshold.unit === '%') { + + } @else { + + } +
    +
    + } @else { +
    - - - @if (data.rule.configuration.threshold.unit === '%') { - - } @else { - - } -
    -
    -
    - Threshold Max: - @if (data.rule.configuration.threshold.unit === '%') { - {{ data.settings.thresholdMax | percent: '1.2-2' }} - } @else { - {{ data.settings.thresholdMax }} - } -
    - @if (data.rule.configuration.threshold.unit === '%') { - - } @else { - - } - + Threshold Min: + @if (data.rule.configuration.threshold.unit === '%') { + {{ data.settings.thresholdMin | percent: '1.2-2' }} + } @else { + {{ data.settings.thresholdMin }} + } + +
    + @if (data.rule.configuration.threshold.unit === '%') { + + } @else { + + } + + + + @if (data.rule.configuration.threshold.unit === '%') { + + } @else { + + } +
    +
    +
    - - - @if (data.rule.configuration.threshold.unit === '%') { - - } @else { - - } -
    +
    + Threshold Max: + @if (data.rule.configuration.threshold.unit === '%') { + {{ data.settings.thresholdMax | percent: '1.2-2' }} + } @else { + {{ data.settings.thresholdMax }} + } +
    +
    + @if (data.rule.configuration.threshold.unit === '%') { + + } @else { + + } + + + + @if (data.rule.configuration.threshold.unit === '%') { + + } @else { + + } +
    +
    + }
    diff --git a/apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.scss b/apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.scss index dc9093b4..0f6fce3d 100644 --- a/apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.scss +++ b/apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.scss @@ -1,2 +1,5 @@ :host { + label { + margin-bottom: 0; + } } From 9d8f116dd14e0e73489a2f1e6de7604c069abf98 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 16 Nov 2024 13:25:48 +0100 Subject: [PATCH 34/65] Feature/upgrade prisma to version 5.22.0 (#4047) * Upgrade prisma to version 5.22.0 * Update changelog --- CHANGELOG.md | 1 + package-lock.json | 64 +++++++++++++++++++++++------------------------ package.json | 4 +-- 3 files changed, 35 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89857f9b..58f0d6ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Improved the usability to customize the rule thresholds in the _X-ray_ page by introducing range sliders (experimental) - Improved the language localization for Italian (`it`) - Upgraded `ngx-skeleton-loader` from version `7.0.0` to `9.0.0` +- Upgraded `prisma` from version `5.21.1` to `5.22.0` - Upgraded `uuid` from version `9.0.1` to `11.0.2` ## 2.122.0 - 2024-11-07 diff --git a/package-lock.json b/package-lock.json index f6351537..be881d36 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,7 +40,7 @@ "@nestjs/platform-express": "10.1.3", "@nestjs/schedule": "3.0.2", "@nestjs/serve-static": "4.0.0", - "@prisma/client": "5.21.1", + "@prisma/client": "5.22.0", "@simplewebauthn/browser": "9.0.1", "@simplewebauthn/server": "9.0.3", "@stripe/stripe-js": "4.9.0", @@ -150,7 +150,7 @@ "nx": "20.0.6", "prettier": "3.3.3", "prettier-plugin-organize-attributes": "1.0.0", - "prisma": "5.21.1", + "prisma": "5.22.0", "react": "18.2.0", "react-dom": "18.2.0", "replace-in-file": "7.0.1", @@ -8329,9 +8329,9 @@ "license": "MIT" }, "node_modules/@prisma/client": { - "version": "5.21.1", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.21.1.tgz", - "integrity": "sha512-3n+GgbAZYjaS/k0M03yQsQfR1APbr411r74foknnsGpmhNKBG49VuUkxIU6jORgvJPChoD4WC4PqoHImN1FP0w==", + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.22.0.tgz", + "integrity": "sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA==", "hasInstallScript": true, "license": "Apache-2.0", "engines": { @@ -8347,53 +8347,53 @@ } }, "node_modules/@prisma/debug": { - "version": "5.21.1", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.21.1.tgz", - "integrity": "sha512-uY8SAhcnORhvgtOrNdvWS98Aq/nkQ9QDUxrWAgW8XrCZaI3j2X7zb7Xe6GQSh6xSesKffFbFlkw0c2luHQviZA==", + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.22.0.tgz", + "integrity": "sha512-AUt44v3YJeggO2ZU5BkXI7M4hu9BF2zzH2iF2V5pyXT/lRTyWiElZ7It+bRH1EshoMRxHgpYg4VB6rCM+mG5jQ==", "devOptional": true, "license": "Apache-2.0" }, "node_modules/@prisma/engines": { - "version": "5.21.1", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.21.1.tgz", - "integrity": "sha512-hGVTldUkIkTwoV8//hmnAAiAchi4oMEKD3aW5H2RrnI50tTdwza7VQbTTAyN3OIHWlK5DVg6xV7X8N/9dtOydA==", + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.22.0.tgz", + "integrity": "sha512-UNjfslWhAt06kVL3CjkuYpHAWSO6L4kDCVPegV6itt7nD1kSJavd3vhgAEhjglLJJKEdJ7oIqDJ+yHk6qO8gPA==", "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "5.21.1", - "@prisma/engines-version": "5.21.1-1.bf0e5e8a04cada8225617067eaa03d041e2bba36", - "@prisma/fetch-engine": "5.21.1", - "@prisma/get-platform": "5.21.1" + "@prisma/debug": "5.22.0", + "@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", + "@prisma/fetch-engine": "5.22.0", + "@prisma/get-platform": "5.22.0" } }, "node_modules/@prisma/engines-version": { - "version": "5.21.1-1.bf0e5e8a04cada8225617067eaa03d041e2bba36", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.21.1-1.bf0e5e8a04cada8225617067eaa03d041e2bba36.tgz", - "integrity": "sha512-qvnEflL0//lh44S/T9NcvTMxfyowNeUxTunPcDfKPjyJNrCNf2F1zQLcUv5UHAruECpX+zz21CzsC7V2xAeM7Q==", + "version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2.tgz", + "integrity": "sha512-2PTmxFR2yHW/eB3uqWtcgRcgAbG1rwG9ZriSvQw+nnb7c4uCr3RAcGMb6/zfE88SKlC1Nj2ziUvc96Z379mHgQ==", "devOptional": true, "license": "Apache-2.0" }, "node_modules/@prisma/fetch-engine": { - "version": "5.21.1", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.21.1.tgz", - "integrity": "sha512-70S31vgpCGcp9J+mh/wHtLCkVezLUqe/fGWk3J3JWZIN7prdYSlr1C0niaWUyNK2VflLXYi8kMjAmSxUVq6WGQ==", + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.22.0.tgz", + "integrity": "sha512-bkrD/Mc2fSvkQBV5EpoFcZ87AvOgDxbG99488a5cexp5Ccny+UM6MAe/UFkUC0wLYD9+9befNOqGiIJhhq+HbA==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "5.21.1", - "@prisma/engines-version": "5.21.1-1.bf0e5e8a04cada8225617067eaa03d041e2bba36", - "@prisma/get-platform": "5.21.1" + "@prisma/debug": "5.22.0", + "@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", + "@prisma/get-platform": "5.22.0" } }, "node_modules/@prisma/get-platform": { - "version": "5.21.1", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.21.1.tgz", - "integrity": "sha512-sRxjL3Igst3ct+e8ya/x//cDXmpLbZQ5vfps2N4tWl4VGKQAmym77C/IG/psSMsQKszc8uFC/q1dgmKFLUgXZQ==", + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.22.0.tgz", + "integrity": "sha512-pHhpQdr1UPFpt+zFfnPazhulaZYCUqeIcPpJViYoq9R+D/yw4fjE+CtnsnKzPYm0ddUbeXUzjGVGIRVgPDCk4Q==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "5.21.1" + "@prisma/debug": "5.22.0" } }, "node_modules/@redis/bloom": { @@ -29385,14 +29385,14 @@ } }, "node_modules/prisma": { - "version": "5.21.1", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.21.1.tgz", - "integrity": "sha512-PB+Iqzld/uQBPaaw2UVIk84kb0ITsLajzsxzsadxxl54eaU5Gyl2/L02ysivHxK89t7YrfQJm+Ggk37uvM70oQ==", + "version": "5.22.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.22.0.tgz", + "integrity": "sha512-vtpjW3XuYCSnMsNVBjLMNkTj6OZbudcPPTPYHqX0CJfpcdWciI1dM8uHETwmDxxiqEwCIE6WvXucWUetJgfu/A==", "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@prisma/engines": "5.21.1" + "@prisma/engines": "5.22.0" }, "bin": { "prisma": "build/index.js" diff --git a/package.json b/package.json index 9fa4d045..34f5a4fa 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,7 @@ "@nestjs/platform-express": "10.1.3", "@nestjs/schedule": "3.0.2", "@nestjs/serve-static": "4.0.0", - "@prisma/client": "5.21.1", + "@prisma/client": "5.22.0", "@simplewebauthn/browser": "9.0.1", "@simplewebauthn/server": "9.0.3", "@stripe/stripe-js": "4.9.0", @@ -196,7 +196,7 @@ "nx": "20.0.6", "prettier": "3.3.3", "prettier-plugin-organize-attributes": "1.0.0", - "prisma": "5.21.1", + "prisma": "5.22.0", "react": "18.2.0", "react-dom": "18.2.0", "replace-in-file": "7.0.1", From 0e7482938b29203b390d1d6ea707bf20b04a6ab6 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 16 Nov 2024 18:21:22 +0100 Subject: [PATCH 35/65] Feature/add black weeks 2024 blog post (#4049) * Add Black Weeks 2024 blog post * Update changelog --- CHANGELOG.md | 4 + apps/api/src/assets/sitemap.xml | 4 + .../middlewares/html-template.middleware.ts | 4 + .../black-weeks-2024-page.component.ts | 17 ++ .../black-weeks-2024-page.html | 180 ++++++++++++++++++ .../pages/blog/blog-page-routing.module.ts | 27 ++- apps/client/src/app/pages/blog/blog-page.html | 26 +++ .../assets/images/blog/black-weeks-2024.jpg | Bin 0 -> 311715 bytes 8 files changed, 253 insertions(+), 9 deletions(-) create mode 100644 apps/client/src/app/pages/blog/2024/11/black-weeks-2024/black-weeks-2024-page.component.ts create mode 100644 apps/client/src/app/pages/blog/2024/11/black-weeks-2024/black-weeks-2024-page.html create mode 100644 apps/client/src/assets/images/blog/black-weeks-2024.jpg diff --git a/CHANGELOG.md b/CHANGELOG.md index 58f0d6ab..c136e5f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- Added a blog post: _Black Weeks 2024_ + ### Changed - Moved the chart of the holdings tab on the home page from experimental to general availability diff --git a/apps/api/src/assets/sitemap.xml b/apps/api/src/assets/sitemap.xml index 3a0f44ff..5a49f671 100644 --- a/apps/api/src/assets/sitemap.xml +++ b/apps/api/src/assets/sitemap.xml @@ -188,6 +188,10 @@ https://ghostfol.io/en/blog/2024/09/hacktoberfest-2024 ${currentDate}T00:00:00+00:00 + + https://ghostfol.io/en/blog/2024/11/black-weeks-2024 + ${currentDate}T00:00:00+00:00 + https://ghostfol.io/en/faq ${currentDate}T00:00:00+00:00 diff --git a/apps/api/src/middlewares/html-template.middleware.ts b/apps/api/src/middlewares/html-template.middleware.ts index 7b7cb09f..6c929c38 100644 --- a/apps/api/src/middlewares/html-template.middleware.ts +++ b/apps/api/src/middlewares/html-template.middleware.ts @@ -87,6 +87,10 @@ const locales = { '/en/blog/2024/09/hacktoberfest-2024': { featureGraphicPath: 'assets/images/blog/hacktoberfest-2024.png', title: `Hacktoberfest 2024 - ${title}` + }, + '/en/blog/2024/11/black-weeks-2024': { + featureGraphicPath: 'assets/images/blog/black-weeks-2024.jpg', + title: `Black Weeks 2024 - ${title}` } }; diff --git a/apps/client/src/app/pages/blog/2024/11/black-weeks-2024/black-weeks-2024-page.component.ts b/apps/client/src/app/pages/blog/2024/11/black-weeks-2024/black-weeks-2024-page.component.ts new file mode 100644 index 00000000..5b380a3c --- /dev/null +++ b/apps/client/src/app/pages/blog/2024/11/black-weeks-2024/black-weeks-2024-page.component.ts @@ -0,0 +1,17 @@ +import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; + +import { Component } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { RouterModule } from '@angular/router'; + +@Component({ + host: { class: 'page' }, + imports: [GfPremiumIndicatorComponent, MatButtonModule, RouterModule], + selector: 'gf-black-weeks-2024-page', + standalone: true, + templateUrl: './black-weeks-2024-page.html' +}) +export class BlackWeeks2024PageComponent { + public routerLinkFeatures = ['/' + $localize`:snake-case:features`]; + public routerLinkPricing = ['/' + $localize`:snake-case:pricing`]; +} diff --git a/apps/client/src/app/pages/blog/2024/11/black-weeks-2024/black-weeks-2024-page.html b/apps/client/src/app/pages/blog/2024/11/black-weeks-2024/black-weeks-2024-page.html new file mode 100644 index 00000000..aeac1cf1 --- /dev/null +++ b/apps/client/src/app/pages/blog/2024/11/black-weeks-2024/black-weeks-2024-page.html @@ -0,0 +1,180 @@ +
    +
    +
    +
    +
    +

    Black Weeks 2024

    +
    2024-11-16
    + Black Week 2024 Teaser +
    +
    +

    + Take advantage of our exclusive Black Weeks offer + and save 25% on your annual + Ghostfolio Premium + + + subscription, plus get 3 months extra for free! +

    +
    +
    +

    + Ghostfolio + is a powerful personal finance dashboard, designed to simplify your + investment journey. With this Open Source Software (OSS) platform, + you can: +

    +
      +
    • + Unify your assets: Track your financial + portfolio, including stocks, ETFs, cryptocurrencies, etc. +
    • +
    • + Gain deeper insights: Access real-time analytics + and data-driven insights. +
    • +
    • + Make informed decisions: Empower yourself with + actionable information. +
    • +
    +
    +
    +

    + Don’t miss this limited-time offer to optimize your financial + future. +

    +

    + Get the Deal +

    +

    + For more information, visit our + pricing page. +

    +
    +
    +
      +
    • + 2024 +
    • +
    • + Black Friday +
    • +
    • + Black Weeks +
    • +
    • + Cryptocurrency +
    • +
    • + Dashboard +
    • +
    • + Deal +
    • +
    • + DeFi +
    • +
    • + ETF +
    • +
    • + Finance +
    • +
    • + Fintech +
    • +
    • + Ghostfolio +
    • +
    • + Ghostfolio Premium +
    • +
    • + Hosting +
    • +
    • + Investment +
    • +
    • + Open Source +
    • +
    • + OSS +
    • +
    • + Personal Finance +
    • +
    • + Portfolio +
    • +
    • + Portfolio Tracker +
    • +
    • + Pricing +
    • +
    • + Promotion +
    • +
    • + SaaS +
    • +
    • + Sale +
    • +
    • + Software +
    • +
    • + Stock +
    • +
    • + Subscription +
    • +
    • + Wealth +
    • +
    • + Wealth Management +
    • +
    • + Web3 +
    • +
    • + Web 3.0 +
    • +
    +
    + +
    +
    +
    +
    diff --git a/apps/client/src/app/pages/blog/blog-page-routing.module.ts b/apps/client/src/app/pages/blog/blog-page-routing.module.ts index 3c28543e..d6c510c2 100644 --- a/apps/client/src/app/pages/blog/blog-page-routing.module.ts +++ b/apps/client/src/app/pages/blog/blog-page-routing.module.ts @@ -165,15 +165,6 @@ const routes: Routes = [ ).then((c) => c.Hacktoberfest2023PageComponent), title: 'Hacktoberfest 2023' }, - { - canActivate: [AuthGuard], - path: '2023/11/hacktoberfest-2023-debriefing', - loadComponent: () => - import( - './2023/11/hacktoberfest-2023-debriefing/hacktoberfest-2023-debriefing-page.component' - ).then((c) => c.Hacktoberfest2023DebriefingPageComponent), - title: 'Hacktoberfest 2023 Debriefing' - }, { canActivate: [AuthGuard], path: '2023/11/black-week-2023', @@ -183,6 +174,15 @@ const routes: Routes = [ ), title: 'Black Week 2023' }, + { + canActivate: [AuthGuard], + path: '2023/11/hacktoberfest-2023-debriefing', + loadComponent: () => + import( + './2023/11/hacktoberfest-2023-debriefing/hacktoberfest-2023-debriefing-page.component' + ).then((c) => c.Hacktoberfest2023DebriefingPageComponent), + title: 'Hacktoberfest 2023 Debriefing' + }, { canActivate: [AuthGuard], path: '2024/09/hacktoberfest-2024', @@ -191,6 +191,15 @@ const routes: Routes = [ './2024/09/hacktoberfest-2024/hacktoberfest-2024-page.component' ).then((c) => c.Hacktoberfest2024PageComponent), title: 'Hacktoberfest 2024' + }, + { + canActivate: [AuthGuard], + path: '2024/11/black-weeks-2024', + loadComponent: () => + import('./2024/11/black-weeks-2024/black-weeks-2024-page.component').then( + (c) => c.BlackWeeks2024PageComponent + ), + title: 'Black Weeks 2024' } ]; diff --git a/apps/client/src/app/pages/blog/blog-page.html b/apps/client/src/app/pages/blog/blog-page.html index 73b4b090..babeec4c 100644 --- a/apps/client/src/app/pages/blog/blog-page.html +++ b/apps/client/src/app/pages/blog/blog-page.html @@ -8,6 +8,32 @@ finance + @if (hasPermissionForSubscription) { + + + + + + }
    diff --git a/apps/client/src/assets/images/blog/black-weeks-2024.jpg b/apps/client/src/assets/images/blog/black-weeks-2024.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c71827c5ef5660d110c21275d432846f8d6cfe72 GIT binary patch literal 311715 zcmb5VbyQnH*DoBbSa4_)91>iLI}{DU-K9v-;##yo@dinNAjOMYi%X%!-Mv65uB8+! z1xkB)-gn*g-aqd5t#$XDGx^QT&g|J|ozcC|zvX`$0CIJh8VrDig#~~=1i-&7tYtMN zrRTc(Ixsa&m4^ZV0BacVK)3<`fV+pEkG`rRv#FUmGyVnu>mmFX+1mSh{Wto*#t&S- z7XO=fj_?1<@&BJmh;Z<=e_;9fa6^3_gg>;2`T|>~}^nei$nA7S1z;^!!w)gV+4?psON5w@bF9UaMJ^P0r~({ zfZ~7je@H)&XE6XEaRmV2r2bEyT{ZyF8Up};R{kdsS_lA;MFRjWQ~#6qKhMO=*2nh0 zs>6MVu^k-&fb((yfW!;{pqd5%h%ElA>>>L9XdClG5&c8EJRgD+zzyI4U0Q_Bf!JM#l<7S$A3fsBmx47iHM0wNGZulNGV8( ziOC<6Q&3UU(9i(Mo`4=xgD9zKsQ*(57S6+eaPbK7@Cd0%h)JmbpXpyWfPw&r2)`2t zixq%PfrUeX^{*em@bJ9x9^ON&|E=sp8vCJ*kMIcyABxq;0oYhL*m&5u4}|xi1o1;D z0GEQ21&>NlL66mzTF5){5kwJ%uV2(7Y*#-+1NGVbNuYH0*dR&-UEI+7giYDrS9JEF zD*6W*IRBCKKcfD(q=!;@3IH}X7B((6Aucum4+r-_3Klj64vQczrNU1=TRbXO??_Zp zeb1wr%`<8th_Ir*&ts@4jfh>bl0if7*1r`15C`i)EgTBKGr-Nk+8U-I`-jcu1%MhY zQfcE|^tO;05O87935oy1Hxnz#DD7Mvlu=GBM9|a2Ee=ifl4~AVUT5wIkuIY!J9goh z97~-XD#R%od=zXP6LW^LwD3W5mXpwQBHp5P8Mu<)+K1f*zJHIrEtm-U{X$E$d~U4E z`3GxP9{+B#vBpQ02(3|7B4q#eOv(ZhVH}aN>Gs1t9?jWaU!9@Fw(Uild(EvjLNjRw z)q@SA``kU~Kegk486A7LKCIFPKHCFL{+<&iIJufc4@0p{;@fuHrFZWlFLCco^qnHn z)U#H9dAkCf8nQI)GcyS(2J1hV_)o4fm0LH9V0!iclnD;Q-Zz^kBkVA;Bbv$-?fM_RKa^Kt#&@#b@pw@HISm-w2lpaX@%gec5S!9VSuTslME>l zYWT$lW*=_YwiRxjoov1D5srKJH)Cz27MOd0&Rqe ztruX+7?(P*YFW2Nap2G7#r*@&u$c*Kc+G0@mhlu%RBkfUct>D$pC(_9B~We*O+2Lz z7Ez_kB@ezjPO9udOZ)t(II%Y&K$4AXZ+c&sTWg5E?Qew+-bP5JS%3L8$!Vf*kzkiE z?0%Hy;8yMW@+>d%I5Dh}XMoisly|L*^)Wv57vElygD`lZ)QC1}(7POvyq8CPLKE8v z_12&|a2hODQPbo75%jomgze)PghhXs;F-$QWi>^ivKqt}%>6`IELH7?;dKQ+M^iEu zeXEHH8O2_>d6fD!1iyT-Eaxi+iw8f&mXx)CkJ;HLQYpJaOh*b2WUVRmlO=ow+*@GU zJ+JyrluV8sqO8VWlBdJXCk=S?MYd|>Q%2eE2y}eLh=3qthBV2=w~E^ZTVK9nrHUpV zI-jp_T=HOK3JsK)0%hBn$*Z}NK0t?!#o&84_n1#m+we0Bs6$z&`de`03o4pCm9_*r zUSthHYT(doy($qD4^J9(#6}DU@3V_washaZwNaRwaI|6{^xk)+=Iij_o^zSeh&)k$ z+w%(Q#l#mpaT>{%B^w-1ugJ>nuLeY$tps~x{TZ869p=MS09@RA%58zmI5Cu_CHUF6!Z_W1#^<$HsyOdxO!sp6GIGr)$rFp3v8&0#n8WTBt zYW==ZR=%tx4Z-%Ft}%JcZBp?b&ZI$kSd|VhYfFjLdjxbQi2Pmfi-sov$Je`xLwi1N zz^S`$u~f^C6XDy5F7J>9aW)=F0W$wDAW=3?;vJtt@^+w7;G;w(iB*TwtxGJ<`z+U6#7zi>6@vtZ?DA%ZK= zWs4$e?sV(zIGV#pAKrlf6w}kp_z$pJi_JtY@Powblo zHhL>&wuY$e!6k#R=OkLqB93S;;ln}hCl?oK{EE7c(x z*Vjf3d2-P{QPe;_x?Yc3f&5twgli(iUO!GlrixEpR!tE(axT2#eLZX855xD8J<^c= z805gaXB7Y4EY`F&Jg;LuDG*vXLZBR@CUCTx-dE?l7nOtHsiXXpZj?aBoL{|@W1C|r zB-_r3rsw{ot%FJlPNKm%$Ze|Slo`>2l1rpw>b4f?h6+Kh0gQQdbDTO0*TYLiTlbgsvAD*UPz7v$3o#(xR ziaVN>CEN6Bn7F|Oi|TV?1N>T>3p6@&dXnA^W?h`p7YyqeVP zt#7-mFgYsOIngy^3nmP7Cw*d)7flm9gM3_2X$AT`SiFN=Fb6 z9ibW^bD1dH=(Q?+ni9~J;N=z$^G#VNtHoGWd(O@*7zgKba(a#tR1&^u2iUDYyprWK znuCV0B};B6wa?NlKDQ8^uPM`k1evly6MxC3dXTNlcdSkz62#up<&k+gO6V61C63WV;r{ zi}1wmlwZ|>^~x9OYh40ZF^QY;Z@#Jh2+z>h_2wN__gZN;OSj}~cw4pN zx=II!d4IRvjwMVHC%N0H<m%Z0o9PN6_^YWa0vG7{Ln@5KC-vvvi)=odbX5Vx6G)y^-1*K1Q>mLp2L0U& zd}ntnUCFQ&6U)ngfXkEe?_69;=93_w6yo%L}{|x zr;FZEX}D!)WiqGVL&`1f%bT@hLGD4`WtslGpcW0!dqI7KiYEmu?IN#)5_kTt!XWSL zS0z@yG$vnE&CGP55D5GwCupFoFWF;|F@u4+(I$AcOiTSWDC8?V&AXoDBpQc~_0Pa( zp@qZtOk4cH_pUCFNrI5WwcP&zvD-H5mzy}6$F+O2mR#higx34M8osQdsvMSR#1V3v zK^ielqet`zPs9)S>F~ef9g1j@g^U zT!P=@DsLSl>Uq(V?imQoV;o(_5l6=L1(Jd9l|GxG-Y8}Q00o3}hqe`O^pDgVwB@i! zHRo96*=1Etnb=OhefqvHGxPh)La@J7*DP#|enl8ETE7oS&N^H#Q8V_&eGjpd1lcnv z;DGKcS@PkV`O33}K(+F}mj~u%?kDr~W^`3DGYWav|@i{)cAID($E&aoGODKyXfJ0^4SmtvpW zkP6E?PS&7=I-s_1)jyIQ!!1{NePGH@;C^um8LVY90vM1c?<_D{m?T?AUr(6+kWOumN&5Te=P##nK74o$EXag%%zha< z>Rx%5=ajZtte@Mvl7%z(xhf*j_h*B1uNrFwFJLWg?ry;eMo9==sMAWmA>vvYU-D_Y zB)F)|3jBLTpU4~_vq2n1z#***6jJk=8KN#3t^MX+Wu`{7gl$vIhhh6__VMxuH{BZ- za+wA))lo%%8geuFM5ESWgJkm$xJ=Ky71i)|ZYex)eVJ|629OSs-zurUBEAZ7$s22N zZxjD2kra|^x#Sw8t2#;LmMls*nKO4Zrn7I{nVJLy_%k} z!Z9~djNHyYfa?Ts5lxUaJZ^ZTD+F_)4agxh`6K_V7m9qnMj`J^-lyqvoJ>flST#iU z*{V`bih%D2#HM%J)}B{`6Q|-=)#Ige`I_NFLjNz&k=gZc)}MoCqs~E07)(v2hy%hU zf~$J+D<}T)R5Uqdy~SwFj061aTNlM@c0E>_b}7X4UhZ;sO9M{BP!=ns-Uy`s&A32o zsce(Hp^@O1HrjJ|x)n{9zYK=thiVX$=*6rmFie=ZU^x2|yl6&lOz9Kr8M=nvt7r4| zo_sE#t_8=dn~T0&!uocjg#~YNh!c=R-ev``*F?KmjzG>e_zla3WE+iE!u@7k2lv%e z#aI(Wr?9zi^-L>9z*C9XW_6N9BP3@|0Ij_G%ZizInVA%+gg8znX(tMl+>bN-Y+NRN zTUV-wbw8M9ykXy*_sqe9zPwb2m}F3IzD|lb)w}g@jDCAv+2O_)N5ymtNLb7U?j zt0H%7Ybj+(Zl4T&Dw#X(9W=LL^b^Xw`uR^UU zsidb1IkJ#GI$2DYqL@gEv{`Sb)GotK;Q_Nf`n9R5tlrhSQuE&Z;_F1x^$27eamL(p|aIiYM*48kUeRy-4)BMSyJmEwx>c@cE!Z z(o6@fome(8_XSZdnDm>gtMH-M0h}*4h#If``@RLpOrW}Y-YAyDFyMDKMRtF3ZEnka zy`br~Voti4pJTZ(CCeT&Bla{+nMoQ8)F@q$;3M$*?-gX#Qn{!kwbF2Y1w`>(>=bUB za1%V2K`A(I`lJ7z+cE0v7MwMHh()0WvmEA3R59X(YNYgMQajCoLlVpurZId`YR3j8 z;$~q)1!)aG1k*GHVMV!VA~nL0r|_x2FAE8AF9t=SFClWMwjo!jtw*8H*A=5l9R+RE z=ZhokB?e0?WiGThV5%W*);tS_3ZetT6mu>kp7Q|ea;U>Q2WGO&^7ULs$&&dSqJEaS zX`p}QL|YWh%vi+|Q33IiVr;@z5sLrvC2w!AAk%@-6;#{fRWF?nhi1PrmE-P%GfR-% z@Cw-5e!8MiU83j4@;#E2w(lu~BtAw6V5Vybcmw|d)DfxC)~2I__~U)rI_m`@5yFZ! zG)wkll84Iryl0?NH>^*Vdl7X-7nP$@jKke1-4D1=jMwNv%Qo{31OZ8!tb%}1sW6QZbK?(#d2h_?vf_*pnF|H0wJ~1)q#@+ap)uSKM)>m5pP6pVr?_XDbWuUuoa4?Z3lM~dW%ude0(<9B5Sj)i{fMyWdb|ATa zooR)5Q)RdbtjdX3F8B?@@#ostbHMb@s0coc)aOzxFhxl$!HDvp2HAI4sI#$^$M^Ts zslt(`EBP}%qczc$9A4p;`r3$>bPp5 zVcPw`Ff9YAb`@5mmuYPIT4$#zG;dYipa)ZJrY6{5&q35!tx(4bwX8Q{4RPy-m`@N)rwhEockI?`xg3V%9hF&Ru-%Ncn$J-}A4vO-mwDqy(*D-e`(?ac8238J!C zdQ?EqluYbF?TT|Hz;fCTVRBdIAwIZinQD*J<&YaxGbvq&$%AIckfC=P2++bOR!%tW zr_YwO18pW2&4H=aNt%HGYT1@kizPfWcUvUbtlKgv{?%vK@lP$pKGjqzBHD1{DSfF$ zA=Sz#!z;=5;v^Qzp3>V!9Q3BoE4@Rrr8ssKsU0iHWG)3M-?baj8Er>_4jJ$M9Ve#3 z)B5{r)*H>)V-@~UEEnySP-NhGJ0c~7R3kW7^AA4&O(NH76I3uNy%WA*EJxt*a^;B$ zQo`C%fb7)D|2rR-Eo0U$9R@PH-S*IRipa%v#?VxwdWRH#B&U{jL{r6aG;$W2)qNZP z0CD{Gya<(9aBP18O|yOPW9fKDU+Qrm1Tlv2YQSPM;MJLQaZMq-!rwijo235KV%lrZ zbR@H_2*HQs|Q`ia4xS!dN+l1b5Pb=}?MuC;gx zmBpf1L(vWPW<#2HM136{XpU59Bt3~F&ik%_zyLCuzJzq_=qE9s>`%AqPk&oR`Q^Zc zOpo5E(ZYX*8dJSF2=0`7*?QvYW2qYdTCTZo)PAOd|E8ig$CjKEoT5o7Y$0r(02P}l zq@>EX{T^zxYE23;(|0yJ088my*GO78eYn_;rhF$i7n{vOHH-uAqxD7I^C+bvqQ!s| zB#j`6sV*%~Cn!$(3}|LhR}e??uuF>?dN_g?ImDNY^U8HWd1EB!>z9c$QVJ*me$h4u zcVDF(HyuA?o#Mk0I9f2}*ed3lEJ1Bw7}gP(5ZL!h47N}}Q{xRq^S+frjmnw^RdxDJ z6b-b?_b2y1GBW+5;ePb6z%B~wQ>h}vTByj2{arUu6HaR-sJzciZE~aS+Weiu#oCfh z7uYDaVJH(!YrpkPs5V8eGyQ-$sdJGxlA*q2$!JIdnryC|DyK7=fx<(@r=FohmZ*z= zq?Gqy=yON1>IxZ>m@Qv-d1&>a1`D%lZfcqyG5gi5nM>Q}2yq>jR1yhY(SUJ{NTa`d zS)LlRwoya@s(L<1O>1e*m@v9eXvneux_G3OED&~F*x02Fkx^Ju$6!Ss@FW=|9qkrS zg#v6TJlN*ZdJ zaS$GGE@M2A2{vg~z#d*QyBYk#$kPCZ)L&St;$Yg}&L8COfoakh0S1!mOE8+Rx_szmJ?nOoHARdk6;S?3@e)(dT@T!T=X%jaG+-;yi-oBk$Yspn z*ESAq)qk>v%ZB~(KU0h>($Zk9iNp)KFjj)PFo#Xd>5(5=e|RDv-aVK zrF&GsLmz9zul+j3NxvyOe4Ka+zai}M>`nx+=IwG9S#=XlEiY)qQ^K@eg-{t^wCPUG z`KUW*M;PhK(ppGNop1IeEV4ZeyvG35OrN-2M|nHzzVE#Mz9-=9JoPk7P9Zl1Tw~4S zH0kKCgq`rHR$~rl<)g+M{hbRaQR2ODWI-unT|tCWC~HxY4Mv(1N#|D=)P40koHa*+ ztIc^!AP4rzOAqZqORPlaPUYhLczB1ENPRua_ zBMEolK67vN@=e@%`Tf?-(uq9|Uz|AGM5fFL=V2TV{58lDCAAbdFnX~=YGw#@5gqf! znlQHX!1I~pV!S5K*DsqiS127Wn@_8xY=$W?yg=S6UlMT^N5@d>$y4j80Q6L-u~eva z@%~?vVS2U${2nXJAId5Dp78TC5dB4rp^1;rNng1hyyVMIU}pVNo^F{Cm7+X-FlqSV z7=^MN!pkQX57Yk#=-YKLLkB)LJd*F83cak0U5#B+9i_0G^v_4UOwn+vYXM!63kiiT zEhr87-rT)W7pE!y{a57A@81m3fs1MJYPrmg&VQd~UNDvd{@S+abZJifG%s(9NGLZ_ z;CWUuxX&Oh{;JDy)m!6jtBfRieRJ1Jo8MBIZ}_0E-0C6)yHu#!ys6~_sCGqxx*mV! zNTEe9xZw74Z2%Y3Q(i5pSFes=`G=BV`BTd%f1RM5IC9|J*-R!SdEV+gu{BVM5VoL; z&3TIImS(YD39Dah*dn8|>}-(jaH|9+p`-QJ)`(yDK}9aA3HFoc=Ef&CD5VlR{Cq~~ z*GI4E&bT_FR)U^y75nqc0bGPD7|7^YyueiU+*SFB*yYdQL+?os3*nZ8cI zBLAMsEUy->m$6zei5upUD|NoL^FK9hp8^o)@PWKykk- z8Koi5ix`od;H_z}?5x8L{gQz|hL=xHT$ZQ5eQESfPx^y_q`#|g zrSFbW25K{RjI`Y{WqjU;PPY43RVpvJHyerYGnUtnQZf2or1HNGf0a+kyx;|%OG)ys zpu9}?>(3Nv7g1t#7m1aEo$;XIaEEV@d+rrp!3l4$p_h8>(D8Ge_4*qvnLv5hOMd#I z-XE%Z-%72UUnBqzfvJ)z`jZj9R5*5m>m_jv#2BvzOKa^-K7OO1(27==cD9_fyH=7% zF}xoe{O-z?=OQWY_FetGB6wX+H;OFl>N?*VSLHy)N@SYDX=uYJJ3S!SBbmmHx;12F9#nA*}3`Xb?p z0+?*O{8T$Gt`>P-yC(dkUB!8iV*v~vCfW9GW}6qTmS|5?hv$?hE0u$8bk^)%*F|4b zzI_wFKF@`e!o0j*{iJq$S%GK@suNWajI91#US}15jHrkJd|PGhJ#9CSj5m8mNbsz}3JDTGym0kyh{I;%wd|U}-<1 z5?>vwqF*&MaLOEBY+|8acOjl(EE>q@Cwb)Vprh zQMg0Up%roT#o{TxiE&DsXc%Mky2C@ih#n0hO5)9dg?qH0Dr=-W=<%Y}ZoE^o!LkV_ z@@j-?Y$y!-8~y0nAxEZ(e}H_~Cpm&tBrrk3n;`?^x*QFQaE)v>!H$^p_oY(1Sl6q+ zDjdv^;|%@5+Np-y4};uF1jcmqt9-BRlyh>-;E=}^UVk#U#GmO2GhVu1sKK_q4>7(M z-Qv-sT4e)B;+E-y8NWtwP%=&AtG#UfRiR>Y|5NG4kNwH7vqx@Hw?8M=(sYW~!!8o+ z69HC^Y65`2#^*Yk^A(zy6Optbw)R0L#xl8Q&4kC;BF>Re9%N|OV8KdZ`U(?mwxZMo zeR3Qz5xb<#CGOuVFltc}Cr{9odt6%Jr%Ra=Qre2=%Y~`KT(~m>)^()^L@ZN%JV>|c zRXS9vwUUH_FsnkSF6N#&yyH#kq`ghwLf%UW;%)5Gwq)lO&?W2efp)C)_UOFbyrtGy zjtw=iDy`%5`TYSwU6J$2mprIb=spfUxchHxBFWYP;Opw)`Sw#%&hC#Yv_~loxy`Nilr<{dcyJ zJTYC%_P0ZYF!e)j*f{nhZuJYSBiQn-0fq~VoY4lcW4K}e00QOI^g{@gy*?IxMsBX> z3dSOUgh^zCR*IVRI0n*sHK6dq7+MWH@AT!}M1Gwr_g=a+B+r7V?!rt(veL{BlVi)W z{yJuRl{~Fe48(6{+bxR;oX4JWtD$|R-5J2>tiGhkoUGT~mPnWOmv-A#2Z@=SQx5*} zs^52MVb1dyK3m$vHKkY+Xa$*^|MFOl9=^a8$h66Ic&cmlqLi$yDT!~zQj;3b?QxHz z`>Mz9i6?k8JEm8b6wEU`C-HytDj|6ETVVr}s-2r5D9&9GDm znJbmw6-#?$i6hE!3Czqq^qlpb|L=N8)`~<{g#3|dK;g^ajpi9qCgf$y+{<)NI3nvJ zRFnJcV}q*VO%UpljuPp(jbD{~)@%}p*`ij6MZLw;!2rcCRhs90HM3&EEPhOebydom z=w+34I3$gVa6Dt(_cjt$b748g|1-@qX1aAL7BX8~!W(Vzl-o*0sQ+mAn`^AZ9%huw z>@Sa`7Jg_V#`y<%zqR5_0ow2;{U3nqx-if`tawU1hbjL8;X*JQzzp0!)t{J1R}VtP z!Y?NE8lRqTuNOtsD951H-Y|fI;C&AlN67mA`F515y}^E7%AuObuqr8wc;bi#XS$tgvtd)Jeg zV$q3}1fmSyp)^DJRL|GSf$-l->IvIGNIZ_TL!f=8prGXvZd6hn4CLD(q@|+(p8(PZ4N6 z#;i4Z8+`rwtq&^aKN|T< zTSZ!6o)5>dGLvbwvn91Szoq|GilN5rW|#ct)2Y-`Ez_}=H@W&VcJWm08KyY9Xq-Qj z&u8o(pkce{LWg@vz>@OMNZQVHAd`YIL^a9M=UX-~INxZQ-gk!_D(8?LouGIr%b2DwG*%aKYYpSebOEKYYTkpoJRa15HTm1A^*G-2!Sn zIbD+(;Gd{0x2uJMM|s{4o=FodWrNv7-$jDtu6T9Ta5W|p5{nz~5ZwXKZtl6CB-uof zS~~Qxend+lf5=5e#0JfpIVLEWCfZK5<`_C}wvte*G`8%OIrP&uyCw^k34TpD(N;;w zv>nqGzAj{rUnAT`77pgA6JeA8x<3S>*W-;hRYoI%+{@vXes}Xfo6MQE>_WOis$PG9 z{9Ue)BOlLvBA^0MevU=B{M0rP`+XG~u)om9@0usMWR};JpLK17g*lSL@?of}B_4@R zg$h{oRgeJHlO6v7{up9wBrx(SH_E7DqCM2k9+pnn_#J$%4TOl)0d`~_l4c@Uv68Nlj7FT|1NNSQ7h)$ezT2IxGtr{#alq^v7ilA)i1avlRhPz5cq}?6 z5DXsOF1HI`(9#h!vPW@{ZtH5jJ#gJ};gCmWGQap9Y{c=W%x8K4yP5=A7kpAJr)rMn zv2B=_>dTh88TOplzD*QwFslSllS*7L)Aqyd!)WOV$~WU@bcR4OS@}z|@6ztMn^Hx7 zHh*c`&$>qArrZ9Do{kAumGO zvFc%xMElfP`5!|*OeIn>Cjot2?fghoA`kN~KSWy?hN-wOZ`6gE{t!OZvxLOK68wkv zjSD1>|9+m$3*u~6TX{mpFNvyOE0ano#a6D$9rS%sia_<&RT7F?o8onn;^OE@1hKju z%K5@Qwpw#wbdtU)FG1d`MPlJV_^B_D!pb~r-dy&F;dC->Y?pa$-uxsVeis*s7(!U;BuV2Em zQ^t!(ZKRZibaPCZ+mo8*TYEeT#;LDp2%=b02xg0Hl9}$Wj^vHNjeo^2dxX~+C58-l z=o6UTmH>WD@+EIb$a8OodVJ2R7Nq@LtK}(^Ngk6UaOR{oxk@s0oAZ|YY_V({t;Y7t z0ofnlA*pI3d-cjZSG!fPr;PGLibksN5yQoLV{uy>h7%b`26S>M47`NtDB`ybh{ydk z&_*>`epq&rLlx`}dAYj1ODH91%D@tO8*U7@^Q~+U5WJhO@9aF}(Beg^R-BY9fa{R6 zoEdg#M873kgq(C^e{jKNy!Y$6vdj#ago3^W(t>JvU#n(rk1*RzB24u~=FoubVIkXH z25lJ*@MS8XM%HL+;D!FwGcgvCKLIxNJjV9{p+EJRX3k^z&@PzJS`F{|p3O5BO}yOJ6`W7T~()3rwL-Wl!$r_ff(u)N;+=5g*DCBUX{>!; zomogp=vvCNMCPk)%`n6C-UE;^A|rcG{dwc`)mZSzNz5P4piPZ?iUJ7+v8z^)K1tl| zfV9^+?Igz*tI_O|nrfA{7ikwbJHLxFGG4xL*V6O#b%FjVHOc7S&0zk856wj)eqlH8 zJMtk$=X`fj;&uaOf%reT$F)03UdHvXl36}w&&0jzp1i{CS=@TuinhoDRH^1^W1XJB z1WO;o8|yKI-AQG$M!wz`0LlCtEZT+zoe z`jEZ%E4@j;v)-dkkh9R=m?Q1Gn_|RH6#Ou0O2W!8SdP}mC zr1~A&FR!x$-N#?99Q68KEPro5`|hflSdqjmB=th}((Ut2GrR;_R4$#txJI+=i7{%r zO!yGgbEe12>jRJyT##38h~&4kObJd?o7|>O%BH}&@i$0!8A0vZFXPCo2w|T+?L+rF z=}A+lSD>eeD)?&abHC$viNX8p%ti>mSk#J%B?CuG)70il0;%y@F$6)gK3NqKQaa_V zh2BhDWl`E@JT*n98q}j`9@v-o+zi|4n@Z*wN4ud+Rt@$%sK?)Vyyx*K4zB|WvvBX( zs06Ws7`8W86Iu-I@oJRznRzT^p#jgD<${Q0s(CGwh{UwhaRrmOXHYjauO5t3NwH+J z7`<%W>c_^*^!venf@N7oD@NAuz=L~b+_rl%%AG;8$1ErE29^^7xThQ?cNDX!yeLi8 zDb`%zrKm^(xH*eguirakVeR_w0&+{pD;OB^p+T1}H{1RJuJ z_ztQ8bh$x`3^WP7mnRFZ=j+{oz9d;ST{EPVsrrl2-BrghUI7=ySR!f)AKC4~lT5$6 zqWFGJ)Vsj+1@|XRvyz|l0yhcxrS>7Vr+Opcm^T_?A^;Pe1KCek#_nXoveEIgTn<6m zK2$m-VX{Zmq2?K|T`A;8e|{O`G|k^aB1yF6e?DR@GklP(mO2p8SN+@zr-#~bXx=S) zDm9S3Rgyb`#*2&RQO95AeE&3K_FX-s(t)vi-s3D{bK?p}wp)$HrW@iUQHwV` zhng82{{V?IetQ{67I)^fw9*)-kI@Meok^Y@?^ypfrkOQNEUiEv{-M)PDPbH zPN}>SVV`zqT)fuSCN9{)Zh&hKP2OkZF}#%dp7l*Q<11#KZ@!%}YO>=?eKpRSk+quv z_O<=vT@C!`+t;M)3yw?T6!>O{=>szi==wx4tszE|G&&>GT1-0zi6&ujF<f8O^P958;aN%y=;4KNCee}4R4&A+9>i|DV)=cp;v`{47%lXyD!Qk3$ zZEtkU!x8ju2EBu{OE41}8L4*AU?k*Qo}^>+wj#qzU0#P8p8R$V#iktBo5zKP$`pmz>%at3)5Se~8zmybgbCCK0+Mg@J(j>tSKi zB#Aexk2;87aFR3-?7JXlYhv!zN#*pPU6cm);Twm>(HxptAQ|7vKI5#msj62a;Et|h z0e-=+9R7)aT}#zBLn%x>A{Vi`$ z->XI8Xs{Tp((LVhTsMH%-)vFRjg1u@0YR*e=^-mM}Fe&Sh ztGpPizS!7#S4$a>EsSseZa$kw*n{oSrdo`qJ$Kbt!;nR-O*NXB6@qf5qP)O)X?o*7 z@sbT4l75u+J9YiyX=dpDS(y7x(YYx4{#*B*V*^^2S6JiIKR{Z34*u+qfy6&=TAw%T zV&>UOQlORt{+!j-kHjucEv|J{n0R~BhCpr=0i)G{_rGcq=-y69{<#WwwqMKHL(7g! z(itaQAc_zv_C|h5J0W(6b&dcKU>4 zr$j#W#DB-vTWp(T2y_a0WeaK+bD-`u2c0AP-q|)NEaaZ9G%v5JA;z2=7%iAxtgZN| zKQM0uUY|VI!H55O@@8N{Hs^D|l=h$X#Q+_5>A{NBPgx}#x!sR^le)i!cb&~v;bI24 z3OCPtl_zd|Yf7`RjvlSj?=|A>EVb&5;0w^Xks5wi=EOI)BbXoJ!1uT__+3EJ*Sr=e zP+LXuFvq>{P|QuG;lb`lu#{Ze(MJtMM%yeX>WJka_j) zAAlQ~cYUa>>Y>3~!;T`BH26!6Q~VlvgdcYQ;4I6~#D({9ywzY7Z9XDM%E23EJB6fb zwrox@pKuMroMCnoQVE5+)Jd}~_ZLDQnl}+LHwNmSm)>V~T5oSn_cdQ34r%t4vEKqm)uA7m z;4;2JQY6(|V1!gDEg4z3G06~muT@63?{T|(`C0f3RY%4?I|HM&$FJn>9EKTie?`%W zMW|Kyq<;@hv7Np@{7eFRdoC)F;Xi`o2PFIJsXkFG?UUAMDHIK|1cVlNJzLCU^_d<_ z6Y((d@or<#a`sv;E_ovy3DojT=hn^Ll?gj?o}n|qE+XiGxBMCq0-$<4P~0LjuSrYY z@MX_(_q(KC#kLL!ItnBxf>|LDNZZg=rL_ZN9m*-j*L^p3_RCfhsR`!zSXYe$|B5O< z9dMY%P?coK%jAskJq`1B)-tvlO5iO+y{I{W&T87%U3#{ooglJvgbWH(VHgLvD*kv)-1 z+)K`wgD%$&^J2pD=Xo#Y*NA(%{-2S@um`h9eO018x_80c(Zi*;SS2nVC#w6J1AI%N zXx(ICHqfXtrklpGI}BZukp0_K2rhNBh(&!B70eD;ZDt!E{F<|!33@ZjYVtKJKTbB8 zTZwp;!qb~8@|#~1{c2>evUXKCV3-h|mtrFAwz~I8~J0iG8_~ zOsB=~9t+fgJb2fDIXL9BAI*Q+bdVk%y=_WpcJ-w^y3@ zcWZe~#vz>FSUIUo9*$GEio@hePQjV4?3_|(e3(n}Y%~-{$MDajOF!C)gf}9I?bz1cIn%a6dTU zK-zsazdd~O!9=Y>+1GE`;Gj>>qx}VA@HdM=DxhM~j00QAZLR}ZLmoAD_p3-m3gH<; zTrxD~UFZfu3`)Ba*tK!2Pthf<BPHA5Oqssddujm9Wg{<6y~8Y2e!YU_h-`1AXfW%#v!0e zHxgO)RKHH_cCWO&{tiQGPORl@zJ&5 zJ@TC&UD9?D@{asycob{jBT;G6`NQX!!gEFgigDO-irPPbm_|2^ zpkfvlrMjkQ=nGJ2MBQ3Q7P7-qzgSM<40cDO8ZVs6hVBeF0D=Uasnb=2PpZX(>seK0 zJcY)W41^r|l>`SiD87}-gH5+oO`ykL)D%vZJlCViH9RUJb3l`iC>58b?owL*0h(1^ z+|V*u5X!F-tP0}ep9spQsP!y*mq};k$muf-cIJJVUFeBl(`kOeYycYoVsq12R}%{U z8}KE57$wg5Yi8d^r=P@c4VVR#1cDY_=ppX3-}lSZ?iQ*`(x%`0poPn9(t|alfi{2V zW2FOjN4!}Q*Aa89!Zwc>1XOd#m4}yz-5AwajFQkS%#&%U+E)~$n~~Y}EFVfODypMa z`m5B0LuH%5bFS=5ko~LITUGs)UIyw|Ea|<-KDPjl^@t>mGIKtDD6_=VrCS63+Kj~` zqs$zuXtv02vaY-6kCx*RMQ;r7{Pki1jQLkUfi$4x8x%M-8x%OC88V%I5CPSDm?~3@ zLjYLNFolYYwJ%d1*zKp{($>hbaI-bnN%VRS)Di9H zJQ@j-Af9J(#XVMt6beIp9ESOA6}Ib{F+dTA6+F*Nz*S2k#7E{%h1ch!vR#~4wS%3a*X(^95-MxAE4 zbsAyzIH9LT3)A+s1i{?AvLCQPxgQVN#o!i1+G_Z>iBYD7HvX#g{NoP?EAu@c=~J$% z1*be2`m3ekm6BLRdKfp-su05^!7YXV#o1XlMAh|OobE2^nxR7&Iwgl0O1eS1I|W1u zsUc@*q!~J-OHj(8I|ZaeT1o_j>*gyw&lfl^PVBSxUTgjSC6HV#B@qn&xjC8L{qn@S z+)|+uaBBBT?vB463*f`NTcY_iHV^f4pRTRFsQvYD zNXu2PutBZWnYdX6UBR~Y$BfZkl{z z1WdY`7RPfngoeY9vnWKZGiD-%6dnwt9SmH|oE8}_w=_wiXD+HUO-?pK;X)ibC`Kax z$xwrtERi1EvPAeW*GI9snfbs_DTQWteF=6wv{VUxU0wEo<_BxY|TC0F~4 zM$zN_H&W^*pyzugwMiTjMQUjunV|h4DnhJRMg5|Uc1z5B>_90R)U ztAQ`R}j$`v0n(oumkX@`XswKcRJ?hp5%ZT~O38(W z?^R}tSFobR)T)+ks2np!n;U2+B_7nT^R`SkLwSH-czX4`i-L;iM4b&e0?0>ZKq9vl zF<<2K!7jAc0C&7HctoIXwkSrI{f&62ZAs=I)s(CJ{kdk4HFXhJXida;jA=#|>n{{! z)hG9(7G7#ZC7***v?MOm`wUS6whnNT61XOZFiBN$!_YW&gkYlTXOnQWZj7EEd^?=2 zIk>Krq+f*7pwTn@ZH=wkSAH0!3XF|}gaqEk+hEwK@4Lm0E~>_??RQ-!eum{je~FyC z!aRKQPKP)sp0fiIrCy&qSagu?+C7a|ochj8Q-~yW=&{z!iI%!9zTr81bVb7B%uMYNCcvZ&I|DiSg^;b^6i{{} z-(EiMpRt;7BhJ5&^tiz^{)zDedsi*4qE#v^U(N6_N#oQZviI3z1%LlTn`||V6=3a5 z(v#w#hcUHE^q78Mt!*CE0e*aM|I_=GHtv&$;Wi;Xj*;n&xE%?|;Q8NyPW^psNOX!b zyB<1QU4S%{JEAk)AsS&j@PzX1u*~?cItA zd)0%E^-(kz%S#-mNrqg@eEBqLu-wM;g@rKzj^Z!1n<~|RB}Y#Eym6h*Wbb;<3y%#? z*SVqr8FXt^gv{QhPj^MY&Q9qP1Rc8tXAWg2P8E?kQnO2sW(-&RoBF2=U^5L_^lbKY z=)=Pt!G|#=DGeW5tX~#Y#5;!y%D&hG@vtLb#aO1tsaD3nyS8x@JR)VY;D6uR7G&@U zW!*VdbR&<$$-gY>}WCf!iXUcdQ6Qh0jDMD`w(eV{`cp8;doX1k(@DqjrWVpM!dHrQi~cTu?p3rxDQcx)Cfn!|g;*>~x-k&*&cpnzlIf}n()Xu#BnaJFI_iA3_yzkS z&fm9LI{-rpKwh-|&Z&+w+|n`W$G1U9PJaUvbV}XVjQ?z{3P|9NwBvSJ@m>HwG*wJyAUmLBs7 z^jC0wXryv@XXVP?PU~_8<~#CBCtuw6k4Mn5WGD8wEqoRKq2(kV$^=(`0^Vm>Fj(%! zx!~$YOHHM$ARS}27`Bl7EOVoVC}K%43x&!klTLUo{>z*7QjM5kcogpl?I_Zk%));H z{QY*R%GM57n$C)nzN>M%biRY>a6dH0D0oGxDAkp$e#K{{x@UAuR+AHpw=rXQ%u|9z z-9+V0Dwi+f2q-8A44lx)*ek0F+i!D#*pkYs26sm_+#1Qq3@d16lBNH*WnEEPFyaOG?2aiX4&Cy2f?d`+f{Sv_#_u2qmeVI#@w z2Kq5+pSkvIRKCi6FgKz3(d^UaF zZ26S*hR}sGsGKXqOWF$Oc3&0pHDnn9l!7Kp`#zrTmDz-geY?@yh_{X$j{6dI;S7v# zs|4*bO$1WM!4PBPq4)oxX&Sl>8}J7alxw*gCa}DQl{$e%GgB%TKV`xy@m1q4>){tm zz?g?L(<=>5R()ooS}I}7xM%zO3onf+kuJD~K4Mr2qMM8i*!XO9z3At9$X`{7t5bqI z?mFmBqWH>vf$j%R=IF!uaM4knADa;gcoZgN@LbxQ660cbfZKW-1q1jeYW6&b9(m<( zr0O*{@dF|b&kO!To6Tk7*pn@5B;y^2-h{cvYWK)Ydz3(d!mBlWe(`v7c`i~TxGOCJ zyRoqo^q{t~VGaqMgU|DJw6!zrhoC|Yn(OE4%yeKKmz7FZgg+X&n8UsB^WrlfPZ!{S ze|z_ms5Jg=BJc%4t?_)_z44zpz0u|=#&!vA_*FLdyG?ijjHdx;^zVxc-8S4riwYrx z{;Uq9a9Lb4$X{sGbt6J@$Pe66fpW6UcXjancFjcdDbXubCBQsj&~wnn@83{oxo85z z7*;C&T9HGl5Eeq(={qG#AXgQ>=n#f?p)i>ppGI_#c|^#krB5& zSO05f%^FCHwM!bcDZD3n-GGY*JFxvCUad=%LYU!VA|5m=uD|L3VANt^g^y(0(Pu{i z;%$F*u06KZy_TyMxBrlAQ#Clp{nMv`O40x`%pFHgu5xQzieL@uQzAi+pX3v@!zERt zqks^^h(H8TpM7;Us6v)tY_M)n?ZxVFNt==s)`!mhL_mT;9nsla_)IQSu2&c<3^{nS z{^EmwB#T(z8qpEe2A|XK!Mj+f3Es!}6~`}BtbW&gYCOJfYeeAS)6z`{bYu#)9hmHH8xT_bD(#^=hOvq=flf^C7+;Z= zp;N5lGwJrPu?u^&+05DLO^CW)QXmE|ttq}7zrjIli`w>LoC;&{<}n7nG7CRWb}MU# zuSk~uuxGel4-XpbhL)yxLHZQu3Bolasf{c}Tu0=lU>Q59qcI$k4vS1(&s-i&3O_TY z-x>48G4(wm(|C9L%^Hb$*6o@)rM6E9_9V<|W>b~VqWkSmfAn!3@*Xinnscf8QoYrVPZM%|dj&H=S_gEV8Zm1Ql zu6&1oIcfWvF~Bx-8{r(z&N@SZGsQYOa;oLvKUn-h;H$#VsUC6Y3nk@`7+T9znVF7ib+F~Qj*KV>k-_(qsUDP zYSh8GqSA3phCdE-2DPo;>~Im2tyq6C)BEyL9Z_-`-R9H9@cP}d0heW|UcXaK;J@y{ zG~*x!0cS<03t~zO)_w$Ct99GmO0|XWPhcuB-)9YWqEz_|>@*(ValIB#%N<;#^7Lz4 zEyGVUGM$Pqx$oCSQjOO?s~b`)jbHZT^@9TBcWo=bV}6lIqPNHs^cFU7GI(QT+)?1X zOIQWD%V23W?67US!W(eTi7+@b3A#7zyzU2d2wmlFF*wP*oUKI8)qEbdSBzemCZ+x) z<5MRPv?_8|YMoY5HY(|!0wgxM`s$i^IQD&j20T~HJA8?Elal@KSqLU< z)AGBndw^dKOU^A%di^pfE|zKmvK3U zo;LG9Zv$lU2a)=}^1Ov)<~9M|ixp;+u}-ujGKr-1*skw{;$gs}6L2PY8NO#eq3h-q zr#FY3T^}UfD?;~KPD>hIer~zdz@R~Ak3A@zj%2J@*$F*+h#FX)wBhOc=52XW0nO2v*p;B?lJJrTR)p(E8FJkap#^6DgKk z)0b%3iGS}G%uI?!Z1jX0D+Ba2j^svGU7;Vyv`B@g2D)8O$r)=i4?8#9^*toJEP6(T z6V>naBrLcALt4k2i$le<_OSgT#ISiEo-+Fwpyn_v&f<&|_Kmu!PqcC6pE?jpREf3I zVWVfIboJVs{BkdxB*bIRPv0CLpPo*SN_NCRm}Jvq>DX93XNo^rx9#EFHhr-%UR~*o ztSy)y6iCbZ2zhEX3w7R0q#5nEkS7mR{#V{k*Xav{W0EwFb83p9sc9Do8@#L=QBB6D zalRBuN|te2Pa+C7che^;M(~9L(1tFn#qp=Yju>iQ#^x`+nj%LbcX#OM#OjSW^ahVI z+D&@b;Jbg(!B>$m(UdodgI8| z(AmlHTbP-d?)_6E7t*bvV?CmlAk+n}HQ+nw1%&>o|CZqGV!;yM^fo^n*C#U6W!3X; z((P5O>37FVWFxv1Et@%Mc`%!{?lb!VeB5M1>v^_cK4jP1~TLxWJm68t5Gwxf4? zA9up1E{{2$rVbi#pL`{o*C;6y#`huGJ6(9RsmfuJL1FIW`EA`+g8s;M51Y4Jc1rB; zAAc*zBWdlDN?}@H11yyn!_0CuNZ2f&b0QyECOfI{wX5EbWs>Zgf1ojo7jKBhVO`3u zC_JV=Om2?cGXwoiB0@v_o8)KOXrI+-4_QbDFY5K`6TW;9k*94YQfy#1B&-$*FYuLF ztRUSwxA9?3rgF#xVRd;oy_{{)qdkRfDtg0Hw14o1=69VJwA=|u?Lyi?;pl<$!U!4k z?XGn0-&})!E;F|q?w_&nQol(e5FX`n*%I#mp=H(ScbQ^$LxDN`WeQpm%pztnG@n!| zIqmyEe4FowsG#W1wrVLkUWE|5%SXI(qf%-_nfp+I7DWl(U1h?NUP#zcLP!GNpmK8U zfiAO&BN}lJVPo5ZhwfNGH23J3%8U}L z6Am23zqZo$A)~n_(+BBIm=Uoo;-R_y+kX;Bnmdw~F8lrE{mlM>Erl(7n}FW(0n5RC zwGXTD|DkRD<{EI^*jJ_-D3qFX%QP#vCG~MBE!e~DOhRQlSzVsx+}3JycmVGw{mZpA zg5=j1YW(L2TO4ELSRG(#kA9}@vQ|HLyyB8}eJ+3fQ1uz@D%6e865XkqT3a3%<=^GI z#ptZLRY}8hdlSErV;_iSeHt;_`GZl4AAHj%-o;|s-*4z^5020_5Y)ytZQE*EBaet! zZ>*8yQ5QCADn3?o{Swq))@rFj!JDNB6Elw+(XuenRi(!6|9={vm_I$v7CkMFWoI=w zGbh>%P-AnRs4vVf2HpRr;N{T*U}{*(H8{p-ks8cJr-`tu|IiXC@od|qOwmmP`wTA% z#aahW0SIx;M#zgy7=%zT>?;3j#@SAq=D(DLtsU+P0iN5fy<4%OoU{7ek^RiQUA^CG zvS0W}y4pvEyy`5pfosyEQZG0sVydJ>Y!>?Nz)x-%{W5Om4bvk)6&#{oSG;!!@36M# z+=h21N^%9?VLYUvmwkkXi`Ld>CuEU-Ya&T7vs?U0R>tug8QkNb(^LSV5xlx>6CF8d z_#YaC+o#s49@RxUum-VO{rzMhqR1{Ycrn1^tOCRsSCogRiXU1O(m=Q(DiEsM#-qYx zjs`-7=uH7(HAV3iWZpTR`|XV#mKq!r9rHK?_}r^3hv$|;9+2OE~*UB^3HTT2^O z#}YP8<`03nK#lKuX0!Vetc`lc=pp27;&oP(4ibc|Fb0*j)(*&^uXe2JfXu!3?r9_h zNWWJnnJ%l;e{|+t-uX3^w%EdX(ZJe9O)LDcv5wX;a&ahH?zEYmF0No*jXyWCpp?FO zbBFHSk{q#i;b%KsPdelk`|SjlQh57|VN{ zi4OYnItg_r1N)_UQ9*CK;kB#C7WrMCSsz0!Ux6r3_=xUHfE=&6xhjnZ=x1#)6kSm` z|CJF+)lqC>P)H4<+BNs}IG(i1Yxm8Vsx2GueQ*oSfjQnH6tnG_{937*qgdwg82{1% zPL1`hyr1t^_&vHD4tM~7bueL>thV^)DL$th#L*;(#4INy?t&XSv1a&EFPbcH#)DHy zZQm~+8lFp7}PA zg5ykuAop;jJO0_wy@#G2!f@la^J>L_r3SN-8a+VP>liCi`sPS{bwZ8upGw#Mhj^mz zc5NM4tnyG9Cls$xI24VeL`ljn>u44?ZF8p%YX8~Cn|1C2UM>0~{i7`Ebd>*Mj z2ciu-kcMB?QVEPH#r@W88)hL%^ou6xWcc%+&~E@`O3xoaYDSRidkc6(SL0#YY$0~1 z@CaUZrt=LkE1dI@0mX|l02Zh8gRWG$Cv_F z*^AJBSQ3cqleg%~{CNc)x}>whX3nNRbv4*~!3? zl-34c@!nu!R8Ln<16h!af3FnaqKFRzw%&{V7z#mqXpjxe_n&q|+sQ$%x$zv7Bby}3 z`p%as?+qPW^97iQeA4I3M!%ZN^4S|j)i9ra4 zX0p)&SnQT(M0xliuzC^Qo!%EXQ{f;h&nj!0{ z(iiecHpp&hd{!ZS5}xlM`r6JR)GJ=9T#9j2t*SY^W^>-li<^XYh|0;{K`PAy^}YjX zPe|Y54p}IhCSIFH=(1M>A|mo$Fa<8#-5dOHu4gh6;}6r^42eSt7xsu`WvN)>fH5d= z9;;1S-vOlP^LU7y4Xic1+kUrrQMsF{@x(2kAIZH|kKb);Z#SV+#<>xU3ITS<&ojx8mlvGz|L!@!6_ z&cy82d2i3uW=mrhDq`>l^(@*&IC+v0b<6aPRC*Lmvas1RjA+S)`~_G15Sl0a)!2g2 z)z=K4_Xb45?c}Kn<=)>|oyS12NBIMP_j9DAGmbKH`I0>StRe!fj$-+|ldV|9=RJ}= z&3lfTT?_>#$O^E{23f)yH*}e?u|H0Ci#s}>+6W6jhjIi7?j{eUs;Bcd(SPl`l9itz zACjuSZ<)t!Umm1d2EK65J4!EJT(mA`7CAwD7|g-+P)GvfC0 zGqgX{sRoF5C_|dSl;};ppBF3sjR3BF*;Fk}(^xf<7c>=``VpY6{2%Btq*7IYb$OH7 z6Eug!+5{vSvca>j@ie6sn2CDJ)d~M-bjhtC%?FVsnSaP3N?oB1b|ed-tk7_(62mGt zh<3;ncM1|lDkSn;B|f(XO<(Ks(FLj!`Mae7Sk8Ew)UxamCgpGD#(X*|5bOwAWZEYp z^z?K(I(K(y@99c0j#z6dw-fz6ID6{O*R{OrEx`=Mj7sLN9|>mEY__65Xp;>roe{`N zkh{Lc>iN!QT2SdZJS21q5!l560mvr+&8+YUHg8a%D2MpRWV`XCgb~W;;k?iz@PoEy)98tZgM_G45ZnPHMpb4Y_=;T$&35V zOkht}6b}F=W47|eOgryU^vtEy5<=`lUUFtkzbu3M$H)tbKfogK`d3$KXL*$-4Ny5t zDRriBBIkxI8~3Ct#>8e8T84Rc_7z2fCc2hdhX}j2P^)vqu?f0EWd8oOw znYn`p1|&xr6qPRtWIR=IlBmiOi!vYZdPa%aWhC&($QuX#L2dLH_axJ*BK0TR+Fa22 z1$HFeDZGam4p!Q>qPEWr>rCPYW6jh_!o!IkuV6Jyt!pn%z3Cw(rgE~a#CK6eeJJ#* z5_sB?bvtQyzCeyZ(nv;df;7)`&1TwqZj&0vfQ9s`q~R?5D&3jsvU4Kf)ua3p9=kuG z6t+Vk93lDh4=T_8xsa``YSz)xyoFd9W2$vH&?hMCBBsx(+VFcY(EVIz*f=ygYsuh5 zex+VK$9wOGP?&t@|6myB?&J^{Cu$Y}3x*Ow8Qf)!U;D605Zn)L_92@Tk=B%ls6f%N zYzK)tO7Mj6Gn?1CKBWO{ckszf~qWNb+?_-m0)F-F-;$`y^MrCdd8q` zxMYdQMn0q81kjbzh75<3w2N14D0$MFVPxU8=#V=fAydFXZDRznUa zqL?@qrc+Up_^H9%LB-lh%wY1^sZ`^3wI^Fo$sG|>o*>|KBC02hF&@09`Qwh zw=gTXK*(67u=dY)aC_UO{sBCRo!9^kNa)&x5F?ETNe3?Pg` z^Gxxw^QZL(h$RXXMkKr%16srin;u{h zwdjQBC7ZM4i*yVne=&@S7u6;k$+F;1dzE$lr?N$~AF`tyH#}Q`7BBym*M&Wo3cmY# z9NzM8LxiNGhLEIau|lsX!LLDeZ4#?@6&xEOA*wLf{Zu0}LW7&0=-_?Oy@=PS)3 z+0pB|$QJd$+Zv)Vk6M+pwF)}5HSY6{mE758Rqp^95U~<98Xx8cXSf)Dr)C0bIE>#1 zdH(fT=?M!A(w`aF3{d^`7WZNe%`K~!sWeb3rwN+wb+ViH`Xd%->8ox7+Iyj&nYtN? z>;9n*-AtoA#-a7?;H@TR7DFuVb*O={X4Pl1O~`=Ja_@!qGibwn+}wpR0*yJfwA1t3 zY$0-0-D5eYmDt2H9Xl99HMI=-isz)}rh1~-Ho*H3pT3l7lH_4w$vPg(j7Al%p5E6_ zlz*kO*U8B}r;lDtwVC*rr9A|LtxUVHkOZYEvxzqec*W#Bu2>F0Nh zt>%H1Mk0Rns|xo6RGgY8KbKh;=3kJhIt`kMs|wbPx##7`KHYeHrotyb|V?Qc?K5zj2L5c^-@ zZ=X3Ltz@gp|Dln`<5sI!*CEK9Cg15;4kk7Vd4tV_8>f&`!r))CZ(u~~_xqoGTpWDgz;J5RS;^KS+Y4MyT5%3g+CtC-r%~wN0sF(IG6zU88SwTBrTGQ3^>{0F7YZCC4AM5Xb8OF8Ws$W zZNJR8<}Q3P02*L*M2tT3U5)v9k9xbYE%$tBtru>U#ipz0W@wE&848nSByP^zHE^{8CC%Pt z)C;U-aVHv7+V6+>_DpK8a~CZCQ$jh0J1C-*iQY;g#XobVqT=9IC3pNoL!5wH(UUUWEj!zM~abeFwfVs`$UhH`|Bj#%T=~K9^qJ|{YFs&#rj+Hc zcS~xKT_KTZLD7L&tP559%R7)VxdHc;CymetNbd;Oq|DOG=g7$4EVsV|3!VpXu1?iG zGn1qQ^oZ1V){#(_CD$y3f*<XxlFYtFMUhaUZ z6VxosM-gI|B9{>w7V@yj@%D!t7hkQ<-1y_G$VdEmXK2a-eMNpsAd%RmNh!I8e<_Pdl9q1rMC`01~MaWSpR;GGK2{^0EMKm z0;o^l^uOHEZSP6=_{8=6u*~FDTaP_27(k>>4)RTpS-eKNu6{!V!LI1pYh@y!yD!|E)74vqzgYVrp3<4G}(uK#;U&M9lHOA5+)I3N(wSL}!iF zUr9=g-AQd-A)XGA0UQymyZliy`oOjs&W0vzpEA zV1|BPmDJ&FIV}+=6QSasBtRcY`^?2Bn2?>xHcz8+Ir6_7xvi{MAD`j_?=ASHHNMhS zFfiGU<;P6cw-*U`FqjD7oxsFsM~^w6Wk)7LF3Qz2FwHCJSPg^nz8vWR5+#siVG25g zL3nRhRTg%Jpu75yy3q%uBLCxhr`tYSRyqp zrfSNk3gVl4IvE5_PPjTZjMuLYbwzV2fcjbB0oJDl(FTF0PV(B1r%?#MQ^Q3bO(!dA zJmAYC7BdXxvA2$pQmJ{m_=kCXiV=b<7DxBSS94UG{%Yj6RfEH$jj`X_mi>M*(uA0=J7P9z>HE$ zj+m$ALj^I%42H|mUn7>7kG>%bCh9p$mZPZN1CsIze?$O=C;`&m{`6_P0M1^)zh!cM z2LnIN10DLL8T4ZopV1u)^dzQmCL;@?-FXBGpJ+GoiybaCV|JpeZms+fGq=L``NrxO z8&cCD^i)T{kvmCg|Ax6%V&)b@M_irJol0abwIh@%afpGuV*;OvpZv=ZJ7$@$13M%Pb8%*)4y^`@u)p?%kKaQFf4(|^Yj5;-~= zl8g6K4;pZN!&osNT)PBgXWWcRV$0sdaJ^ZZ0IGII*p5i*_!Mkt-!P66w?Lvk(bE6u zoqXD^oU9aY6&btw;qN;PkcR&1HFQ25>uRYRi8~*cL$9&Hs0P-CVbx&0(Geqx5{~xO zdI|;B+|m1;4|cGbWbQEZ z6)j4M@hE!pqYzlJWPZ+~>Om7mRb}Fi-1BAZ{14?|gJXE`)<$$Cn&+KH+T5uw-a0ph ziJc;ETbbmY&r^9l-k|koA=HTotDO9EbFgw8B{P$)qUyb6uxHLBVo+M8(L2_8;H!DD zmn9WoRsGdX{>a1K@^qSZ9tZa(>R`Rqv&bR%lfGgPtbV zRCD}mwaLy2XWhI#)E%;soyztw$--WmmkB$fZ#LQ0HTHLe4or_%2aInXa+9DvTffzV zndg%x7Tuk=)`5;pPbGEVG1k<3Ym{G3MJnpy>cchjg#7t~m6ZG?ET8X7NS*Env(*|J z!nj4YYGTNMM}G#uJ!-wnobXPvtQEObxiCXJy3v z@Ps|5oS?O<)@I(0VLtG2ZUcNm(plob7mWND<}OGukcM96EkVztV%)`QU$wY+otF=G ze@CWD*Hoc#toPf^XEStzNKhN=b3d1*aVvZ?@GxF$HgcGvAarf$wwI{TQld3ae; zEPtxWvCZRkRLSxKBuzn}&~P`a6+8!g?ub2f#bD|N+&Q`T3oQY%C!f=FQ)nY%x|><7 z5FczFtPS+dt2|0ge*1!Y1!d`*7Jqd$L>Sns)$X?K7bDO``7g0?pF`zL)n>wpT|bmm zLHR^VsA})&&to0Oh5~QH#?pu=+*~#|fz}2USHh=e7s5&Ezm&pb-(JN4zJvA`-%@u% z^bD28U)WZDc-^C0Ut!?e@xZ;eD0yyT?whZa_C!00R+E`0+58{c7#=cp&j5$c@X{?n z5(Fh88Ua-%R>2z*8QS3aM{A5ktFj)%NbbZkpU(^M#74Q8?P9wyEngpawI&K;bw*C- z4C^lkDv1LFSnnn{zTufxK=Ew?tKaA)a>_a#T5_}#)3n82La`#d8}ps|n5yTzZ}OUC zJwIi+6S2cLLKf_rBQ2Z+Do=uYZt-A8GSTpID0vec5;*(bu24?+w)$l+YilY^r;w`! zuLf>a#Cc_+Z4b>s?C2@OaeoAX-$xrL62nhOt=|!B%$JT@r6c?cmv`zVUlAjmfiNR(ecc$z(MF#otkd<{ z-%FvU!Eq{WXPcw6o~3t?`XoupRZ3~)ktCBADJh3b*$-x|Zv8MNPfLO8QK+$PfW5yY z=7gzTkR#=e?T*&Us?|eD1;Li=wvu*bjl}Sef9GgnzxiAjdk2CT?Z0Do2?O_hlGlk% z8aL~x${gd6NoA4t?(d?mr`Fp_nkau#O4nw4U5u;=${HK66<_^Rd*K_hA|hbzsm+=} zL)SKjRv~vOotw=aiM7|<7FDjL%vWf$(o)hOBHGcer*lj)gvI?!?Nlq1bswL}G{OFt zWmBkQo389Z<_`l?6~tzD7WrtbFlBgo%{wZe8XTuMe=XKF(v&)vbSDld2Xu{P+SM`I z&_019IS=n5oT1qcrsu}F->Zrl z?K~=i)-(+3C3(1ctZDe`LaV_ry54Ft4q#=>dw-*SG*b*2u;xqxAiA2~#z#0ex>!2) z$Zs59`PP&VeOCCff+p)P`b#6Bgu9W_u)RD6xw9AX9}VAxFoy-ZB8sg{_FRv?3D^7n zVKIuHjr02|{#mnUi_!vbBnc;Ts3=M6X(ME?V{_!?7`~%sruHW0Vn;<%q5gnRqj`aO zQ2#i5j13)4lEK}rpmQtv<4%zWaH+xaJ)?n9R#t&sB&G4hb#1~|EM{-fXd~Od%b@4M zd*5aG?~_?(;cA2fwdA_-TJ|1r_BnIHl)u})`Q2P&V2AlV5D-h7pLLh zPF$)fYhWWl*Wc4zYF$-M8%Mn`rXLbRL|UJ!_rOaT1=WK zGWhLhP(brKWTIMnM+^wYaW3^yRV@0yLT+)(*Cri09l*||3?H=@YWm+cn?Z@ZXy<2% z{GN@_1FT<*XDDGhoBWuCVz%g4FEgme=eP~_iK18QLUc1^)CTqj%b1~nMhf7pCj2|S zrL)V6Z4Ta8+Qa0E_ZiuFM*9~CyzQJjSHl-ZI{%?b%zeF?F!#yH+-8mVz|&IeT68G) zJO`jYD%yv-t33-YtvWV3-puHSewR#Y@nAIiZs?Od^`_CnziY8q1V6?-Z=lX;rZ3Al z0$;uIV7{v_=j$ly${fAKbQc7go>|g%;+4)VRgOs2`wuY{Qes7jr?U%iUD^y^H=Hg} z#SdD4?IK?p80)YXZl)WfqYc3S+I#YTq5BPQ>CjZt1%=n+;cST z9U7hq0aV%P`A)K}m}qvGr{;f-`R4|h`iCSV-)o}4yhpYh>a;iG|CuN@MW7Zdz%mK>*bbMBJpgQ4rm`^E2qqmh?cb~ z#&?!;;{Q%CW&=Rv^QXBDfK0j9Ui5>b(TSX_pb8I5>9pJ?;dHku#~*V$0096;UA28? z-|Y8zx~qA8tABiiu^sf9$!p6?9IIgw_l`LME@CBWF8uZ_M@{PCw!#gk*JW+6V+VSgy3CVm{18(>7)=e*}M&(0#ch&Tj4R-^bC9@-XJKXUALCy<*s+9kx>G4TRyS#sz8t^9 zslC1A?Uq0)V;$W^Bxfn)!gj1bp9DQtKX+RBF9+k6)X~U$chksUAtRRLs&&)r|CB6r zw?jq2rpiN~ugDUaER$vCJf$WZ0R8M~^9oQ1cw>L+PnmrVDev2Q3a7ZPI9ppJ`t+tF zT+xtbrk9?_TTk;{$MI9E9ScZhzo{K09xYC1xiBfcUk;-qn~Y1KouzBgaJwycjux~r z>J(a4%Ydkj(V^~`p(77-9`vs@bw_^x?n=_5JhEFA<9GuHP8!-nJUd0yqcF9#*Lm(c zeR~-!PXtTUR2AxRkJjVu6GxNVeox2>j3xmku^}9VN6#5}t`750PH|dP{Q@s3a^&^2 z|87j+Dh=uhN;ROW*fO$JzHv)R&YbS&)AE;$El^QZec|Bv@urk?l^U0npdAWSW)~g? zec)zDIl?yP!{zyzEB5J)C_>&9<3zl;RIgpVhS||XX)j--Y4@pg>D+M9hJAPzV&jrh zEum1Y=GAeo8*$MMrsI%7+BX+190K9(o~@v`^D?)1L98dLVVSUFJ0_|KeL-$oA>th`Givm z9j09A!xeHOYxT3cL`dbFae8N3C?JR-@6uVr7~t@#cF6m6SBQ<4?M9|bvUbf$)1Xy#u5A3h4b@aMPIR?66QL zc}zw_-xsIh@A~8ROS0<3F^5>?TuRjmFaBAeMI%6VFD-&TAQAy{pIJIdaB4}BOz$L*pGy)=Mn4q#oH+= z1<@}Njek#-#MzU@va}PzbVq+Uubn`cR8<-Y^Fgnj=Z?a(*gLhj+H#v|szjtks!_Y3 z>v?H{`bI4;#vk1f&X1|eBbjL%hdQRF+Af`M$#Mup$jc!SDaP&}EDFnvI+JpZShF>{ zYO+`LUUpDdP|aDGb?c6gP)1J1&ejeTvn&?3NoaUR($19Gdk}|1*h`WqOQ@UP{@@bf zU(7?%$}n!ujKNd_RrI}UN$>$^0ca1Ws^9Cnv(AA31|c`KAOB6P!S|8QR(lKfJnmEM zU62itO&y75w$Apf0w-Y?C|C;Mpn3MHn3}c`+#O%+dt7Ls_Xmx_=a#yTHQ4D3>}^EG zo|P?0DS?pUEv*>#I7lUpeJ=}`JSOpejfF;|3&FPFbu-tHLc=Qq5>Dqk+WbG`YHi$R#fX>XdHx%X9yH(TZw_f%hbaGji@u8KlB zt?c#Q=FQT%t`-)^vXuWEM0L&2Kx4Ng`dpAu3B%Pfq68oKBxm0i5xS+DyO_jEkYO|& z-%0$bEK?C5OTEnJi{@gSm~_=RjQ%&;ADCJ+FtYi!+?=+g1y{c%6d-~~POn&DU}89qPZNCUyaz|3qr-?O?Koxrg|*MTUV=$!=6rO218dCc!t%o{ zvZpcTN75HdwP1HUCr>a+;+6_iki+RM0Jf1zH&7{;9#EEP2TUDd{b?9wcl|0hS-@IKDvi#2*2DfI|f%e+d!1k2NG{^Jn5imybtv+ru_K+$w~JhAI0z zr{_k3^#>^`&*gX`fDxTFNF)WLInkx^98NwwceGNRJII;5|BJIlMAq)gNjWAp(92zC zIQK@-FVW5OhLyJsF~Y{!2`&tY{~u*%`4!ds_2HqryKCrBhVF(Lx*G(BmQE?9OBk3T z1f;u_4n;(|ySqz3T0r>z=0AAW^Y*+v=RRxgeeeCbu04M5FFW{Er38i|3b3d>rZ!6gh@yM|L zAyL}}^Dnj+ioeu8zsc-syw2VLa#U;jP~Ueaez){7K*y z4=G*htlHm48kqyLb7Qn5m_-3>c5>dDaAXlC;UBFlM8jBT^~dEv_cHQ=G8=Qk1p9qt z5g9dkC46g6BDDWFYImjZyTMX5{dHvLXBXft{yj$yMN()9Q#Gps8lC4pm63ALc4EW;5iu9jS21jR?*~6gf|5iNXy(Jq3pKK{vL0Oo)WP2?6f@7N?A}P?_Op2&0REHur zRO!;(T+@s-|3-OVn;MOSP-rvw>Y0smlv<$TNUr$n?(IW+Hb0c&_S+K;3j{sfOEQP| zY=Z9Zo0vZq@7nD}DKjszqe*^63))Knb8ms?kgK=*1-nGsyl;BClk53v?;^nq-qdtz zmARF7)L-P{xAr4(wU+PR$3JVt&8#W6zw7Cruk%>kJ@IDIpa($yaDA!jj(8L8hJ@pV zFtRh$`fR-6{oV+n4hS2<6`BIbRn?3A2hi(|Zg(PT+WzM{!?oR+?-`e>h#H3i!H9z= zISsDjc=^DXEIy#N%Nf{C9&;YuCZEq_+?vc5&-Pt(M|?}tfK>jM(_t_cTz7fZ>;o~_;6|+>C`_WeDs@NJX*AkRe!)vFD+9>{;a$5Uw4bE7<(Da z)P2lZJJ4;HwY{Ex9N4BPQTG$>|NRrdt*fCxZljZtC^&((>Fa0xSPz#OlnKm?XCw)A zfL||^lS77VcWw*_@CX!t$tt6SUB<6%vr3kjoTylu^j5D9`?JQ~YW~S3Vgnj!3@aO7 zHEMec9a*7KZNXpD!eb-0PCV>U6vSgNI-5YK~(THhvQ_t%ij z(~cELL0?@LuU@Muwz$ej0Qdt+Av&O76l?Zgd%F$^?{@KXUbaWWUjTz7YxarsNIcch;yuf7> zCo~McH1c!YF5TxM&6AM1l&2ydvR5680V-Z%Kc=M>oN9GO@R$0*%SewfQoIX>^RCT3U%Z1mjkJMQmF2jW-Is9`}mV_h*QR@8lFn)VFJly79J1G2zMR(#3K%l-l7 zmh#a3;-)cjhXNV8mHZ$d(bz!KbO)UCIaIYvQjLdTD>}PUKSoz91$vB#;R6>Do(^kg z0zP=<(kpM|E@umYYocQ(3{$jPX^<{2f-S785~2;&t(U)g>8TV=R=LqIWQrOrmLO7ULqO`QWU z&@LnJ{<&4q!=1z)?kDcjoa|IJ1K>PYsE}ze1f|0bJr@_S&#_>e!Ht@UmvF8g_qSt~ zAi?9dGv!D7Im}OR?Rw|)kOe>3tynQPNgeqJ8n6T%r8A_!S37ckGcKFe$u9d4luLnD zaZ?sa=@QkPILtBweIMu1;`!(KtmU&{z5&nG5)b|M#9y?YOWlkvjR|c*56MM^HbMU8 zv@2UG5)k%i(M{xs%*OheKp4NW1m}#&fhTL5_!O>sOXDwtxl~|&*&8Xwk{@p|cfcFK zP)w51c9Q|ML>=6_xJSRUsEnYD(FYr$v56}TXQBfgBeFXiEW|t7_b%d_RL*21ekONyy7~zL9mnHMMLo%4G?S5Ldc<-Su z13>1!reeHdP&~LwLc`(jm%T26>2!<(AG>7LI>7{-iFK{FUQe<4568e}Kq=1KHbdf3q#xyt2c zD05f0)F)UsL97m8gMit1nX&)? zbjkg$Oq;QfA_#3XEi>Sz@>2H?Oa_hbHMP0K9!5TdXmS@^M@9nzLxRd1-^&lJTPRYF>C(IgC;^gR5jbVeS#yA=q%~(C! z=NHVRZL_I5rw`&C6ZUbjrzFOq566*k{r0*0hp!~vSlciJ3>)Qob$cabytA$7sJ}Hn zy6olM_Oz(DFd7oVc7%K7>ftOQDK9~*qL|7mkuj^FNM1}ZA3DbcbP?duUqAwwP@=12 znGs|S4ByEv5-qfu*ymFFlyD-TWyn1ak@ifpaJ5wa^`sn0d$QcLgIa0H>^4sfGl<=K>+{KcS(2WHC*3MKAD%kh&8vuuB;zDe?>>lI zY?96E6Uc=I!w-42MOLyZs?qs$=K-_aX16bIdwVS^;jK<%?wnTDYr5sh9`Oj4nPem@ z-zYDKozw+uPDUj~@1=Rz$$1-(wVrGrU&vTu?X$g)df^}&q%wo6Uy@;5*63|aJO)7R zBsp}>9SGJ)k`Tuwwqaqb1H(tsvU0a}C(RMqcF~W(q$9>+loRdcaS3>-ZeL9TQB^Q$ zs9`(&W2R0pU;0n%&-in5K5p53J*@ zaniSx<59J}dVw2}l;>=(NMsi#wApwG89E;&H4DwG?`D4rb6PCC3BBmP(6e$hb<#cd zEb{qDa1C$#5AbEB-X}+O{);vBxA!;8XZhPy=|wlg`Fba+Vj}92C2}sJwnM^Nb)_yG zQnl*^s}!F?dZ)`A14t9(G^K36o9OYU$&(l;w5bi5VL~|TWvVmAbM;RJYmH}|AY5o} zQVaSCM|P9ii(wD+|1v$pd}D#3qjlhmcY>p|Vn!g9j`K8TE1 zd%cMSm41WJXPKofkpC4q_Of>>J1ULg^H&|sXo5?Y^RU%Bn|VU?r{qZ>D7Yp0e-dot?L@^8`)NnM=g|HHPWbOwC2~LQF?6HXkXb# zDH((d=Vp(MqonZ#8;A8d`0jL`!l?KfxX`XTZ#NqLrrcNZA#l zs}a4bZ_2!@=mZyjMVjsKrWUQ1uM=^3Qq$H)!n`SF2)Fzn=ReXNYt6(r&L00Od2y|J zi*}ldoptep@Nb<3o%mVC0oQSrA6vt+#ki(>BdA?fM0LKKLPJp4M;`H36GXZ6?xnrv zR{+x|#x@0FxMCigu2j8jY=>}y-cRh>RYx^WtjGBy@$?jIzMdSAI{6#pC(kpBs(fQ@ z22ZlKhMi$=a=7xm>(E?ycoV8mjJ(e}h{Y)f9V z&l^Ut2}x=Ngm&rX^n^K#M?+u0voOUp0v_aMim%+h3e9cx?(*ksxSmPWHY(nH`1{my zEkhQkfIm7O7jL#9yloR`Z}W%aVPIX9sms0heWve9LwZFoRV+r=t@PIWTmkQH)yu^G zmR*jwA^uE6OdxH;MbU+ALoTbiLy3viI}vT1{Z{_rkLl%GS~wlskJ=*rHa(X~6gU#L^`z)d%o^ z{3o#9?WSJ=7{Lz>svCJIZGe>di1q<7)E4)1V{1y~gDsWRQD53PhzX~iVCwSyu@%np z?j(JKL@Ox; zg=dbVl#X#DnC9;}S4w>xqYzhBeT9-}vGxp@A!#P!IVPK7=gvKXi5M`cg={wI<$c?# zT+xxjzyZSNDn=K35*6SK*bL@hagc?al|-#%-EEDXNDR^0wKn|A$J7ou)g5cH8u!`8 zC}HJkUKu4VbbDXXW!VE<-wgX3M5d09F#qb`;zsN+IQ$>LB&PzYJS5@;XX#P&u-g5l z^=`*v3ij2)81L%?n>nTEV~XqUwrSt09*Z}BAilS3uXoUSc$a~t)y7uNQ}_87WrxL= zpeF3M$0EO`H@QXur$t$LBYU@l`5Crs7WriCYdG%ImW*%2uKh6q?z;?Hc7uUzlfxa& zAs=r|cOu{&&clWFuNj*1dayRieR;+6HKhExww&X19nlKu&P(pEw8(h*{1<=xgIBg8 zAbrJs12mS$?=ItSP!LLi5`*Wi*yk9vuxJfl4VHJm@_a${W3xO z9i*r<3Un6*w$E=BhY?(C*_3AXUPDt&1lT6-Frnmz#TGMHD6sh3Mc3%BAb!pdQBJVo zc%!5>z{{3kX*0b3FLmUSNb$;Af&i`WLpexi6p02_pGXuj)$}P$rKfOOhsteKpf`Zl z*wA>n=C1JRL#w{7c5I43`IFhd8SS{8;;qq?@szVQ{v~+E(4n(XA-5-V|L)dDoHwxg zWTeD1D45L2%G#UDVoB)fC3V*Ng8Ukyyz=;&Uzh~5ZC@9R=;gr(eM0BqQOaUCt0f4I9PT z*7Ld%EYw3Ly%I`OXRsD_fh;BaMg2)n*GW35Y$FFZo>UNtP7Z{>%bPF4F_U~jRnmSJ z@;f*{_!1~bTf8+~1<#UCPKRJGUx^L2x_&RKD^Z{cQfxJ`#>2mzkkZA$ne~>^2DTCk zV~3Bxf3N?@u-g<|W6`7Yxvq@J#DkUUZ{Fg2<@LMR&&hNV z`M$nBp3NCj)u@y@!ZwV;zsFEm4jBOIQFv3xA1pW37gZf%mW7MemuD)zR@Uuh=Wn39 zu)%SXk?Lb*tXZ1-e#*?5$zB$cj1bk;Yr!$RJ$nCR1*My0em5FJ2gMrvSm^Fu)q+zS z(nA?3RXIw$HVJk_+rOirX!+bm-B6g0v-j41M62ANdKV<`X zv$wdURaQAgSo{SzHH#&^DIRJkj2~QTefXhEOqJaM|L97rqTLX!3wNneB7069X90w6 z!Hm|*4aq!2vgtT`uZq!+9VQ0#Dr$4~;Nr zb*O&woS)=DAZk6~tG!Jo^{`68U|RiTAqTL;&-=ph1eOlUU*Z;5^J^zPkA~+BoIHCz zQ;0Awe@Y};fssc&J?C|e*A$5q+ciU(L0;?6=c-qf5c~qZRQX|M1Y4Z`y2AC^bM>BB zor^ziBUJ=W`X^ml6;95x-1(qs&b*uj`r4wrQfRUSIzHjwaRt9y9yQ+PD`_E|Ee9@6 zxL-S4<>1=;xn;WsnDc!9K;={$h`-^!rieBK#hWBCK;G6P_-IC3lIwJ~?)3=E2$mUf zeP&XJlf^Yk3NuYB0$xBy!DaZjf_ z%flOfe*cZGlxGU0z+xr}JJMRN@%l)eon!A)CJ!FiPElbL?Kk)yNoNL(lycI=c~=^+ zFe4iH##aAxRu05W_f_8j4vZxhnhewSa^*}KaD$pL(v;1e8!X8G5!#4mrK{S33z%_A z2HDyV{j|DgI@Zh9V{vd{AI2+G2KhmKuh+?Or2K3)8>d=Q8Qu}0nGxqeN?tL_f=dgA+-4LVB#UWE*BA?_hU43 z8}wrCDb3t<>TBK(sTLo>u<;Q&+1!Ui&4oZhIMg96m0WSHW1enKY$?*W9S?&EWCK&GFaJbrA*-hw8ywjJy`!_@7s_ZE_J zWnR_RtF$x<@z(Z%{~FOnQXM~^t^AF0!2}7UApHRVA29MaLM5 zxjV!&NM9_Jk4lXSM`RlQ`lJ{Bjc^sWb~7Nj9FAC%BcKntegBa(BK-aF>VOxXPy(Pajibd-)dGQ z-)s0D3FR}--@%&p6e_NUFwXS-jOf-C%zZvmP~p#aG1|3ZMY95e0F!`=-7+*pdJWX=~-&n^qq>@2}f-6FC2+W z7BEXPrKV8WZo`4e{%qjY-FAkm#>zx9#DNI!HTTC-wNb+=RN+R{B|(mcS0|oNx)k?L331wzbB30 zHFoYunDuIiC&W)g75c`Ilo&G&SQ~u*9d2Ta!&4xM5iObGKw3XD`#d;rq3DDLSW!@;s$x6ZuFlOsvSR zN=&?)SafFTel+@lAGNvuGRF<(lwAK7wLq9&U2mi=^l=nVldxdQdgUSj($Fxul>&En zpyS$Dklr^J_%O2en#2r`HwQmjOGvmbXH(qGRTwI;PFQP)cL?4 zrP!y#7D-=Gukk03+~>bxgnm!-i@Lecwx-utfpgIJ+fpZL?_vSJJ_S^v#j}{!qhOSd zwx%j3z2tR<f$N2Ybrl?gV=N*}T0*znIR~8>xc#SZuf*u&N5VXG@J2Hu=9?jg0^n%WS#N< z*53T7D)DBLNboWv8TOl;Sq9teEnI!)Fr_R-5+#A_KOrYjSt zvnitPtj~^;VfImoZ>6yeTY^veVwD^?Qcyq|m%Tm_z9lMlmg<`GRxJ%;?TDgcyE@>y zza~hnM=;snpUlfzg-07T<+%o5?3Wkea-7Q@8pyX0P&gVu9P)GY>Tr$=E3{)qYF87x*8*^H`m) zECk=k%pyzIvU23uQ%(EfLRSqJRDww&L}+6r^WEtO&&ukFnMHp4Pk_tP1KUOdD@=U* z_4qhU`h@xx|L)OHPkNn1FDl=Lub#u|GhMjUe}Fm-J~jD0YPeqv;illDfm!qVTc-on z4|hM;Rb;vJ5ZrqxGrH5td5cnLw?&?qYCPBWe6xH$jPYI6(;jSacT(UywIiARR+HWz z5P#OkZGX^q-qK?_+0rb#p`)`g8yyuPeI5+UlLDi88T7+`VZoE8Er#JE;3-u{^PzG> z*<#6jG7CuRis{gMe2#;(EUG%#>t78l4|{&eZ=puHtY`FrD!5Re=>0}kA%^S1q6QNk z?Fj#OyPRN>FC`bc!lU$BxTnl$MJXc{Lp8L~!Ml3Q+6hHh;QOJ(`d9trH_8%at*ObQx@J9!#XH5lD`7Bu7s({mpq;M`JDxTvz z=p1YA6?_QJ$5tjL`Ex~KC_&V&AL>`W`)rj_1V;wYAC2W}C&>3u;B7x{AY!bL@Km1S zeRWuM@ZLj>;Gl%*?jd>dKzuQ%1{GTOZqy8*F{6(pBe z5<;dM-dB9B5&`MZN04#>M`{L_khFzTXESIlqy^jqL(~etit!YNuQpNdWdwPT=DBur z-QSgw?&X)bd$5NKdk>O9Y5-yrB@Yo_V%uBSjIXie@}C}VX`gae`u!OAXXG!rJ?y_E zH->3Nv=7P)zQMcH`46zX0rj4EUvNu%DZ}0@D;IV9YHEJ6ucf=`L53~vKfnoD%JLFW z$lUCWC9`;E9-Rff4P{Typ!qe7zu%*MleE3ND}CJGYk$v25E1sx$eed5-)s*8S$$A%B|?#)$`eao&FKK{E+k>^Xpq`eY#P;PcWJ*2{&&~15d;Z5h`i5poAAxG$F61hJv%;%nZ6Q{h!F~tzW)Y3ZM zq#o?D@w(=2bAisys47Z-CeX`^XB55YvTk*bY4#eT`c#Xkem#2QI!zB+`~5DEQjFDS z`n$6Q2GpZs+{zG_mVlo{sfIsXp_Y&+h%r99qmH)&!!m+4qVa|iP#*{D?CL-IN%7Q}vT<6EXr zRQ`p$4QeV}2-jC9lLj{xg{0~YpAz_ILxG>uE?Q(4j!V)aA_*Pbz)*>W6Zk(vMjiAR>^;I_?(QO2s#r_*}NbsO$Rh zZHV7$Igb{v*HI;kn`@BJ?1j7kAwp6ath_n;zqDPPU)A)npUx_o-$`V=z zF=(DMm?=0^AXyqB(#RwAmP0eudlo5IryH$W+3}gK$%~-9O%`XkG-3&3vF9PO`yp+V zy}UM5G(V%tLn|`lU2rygW>|s?Dg{3VEL1@63Ms)ed?iC(8-;)gE^CW{$~f)h(c>QB zn}Df_Y51d3!CeuRK9|6HpjCJM9LkuT2H0!QhUkENnt8U8jP4M3_sdK+FKOa?XfA4q zQA{eNgaqA)>5I3#l3%;sNSt<_5>~gbCXC~N4XVtB-~ZWdI&@kk`hb@k*-=Z0YR}WA z&(gq^RQsVbkfoO!j2>V9<{Iv7Hf%ylkqL!r?elWg9IC#T4B#Hu8FF^PgPPiuESn7g z><)-l+I1WZ(<25n)mH!n-`>4s6|tK&J($uehgtfMi+b5cs@P8ex zMgD~RxJB;+%jmVnO3dj513HpRhd{jDWl+u|kSn(nNVBeFIqF9I2&+Zv)5o#{F68|3{z6WC8L_e3@!|vkCXE7bIZ~v$`UCR zy5avuq>MC{euYXajAd??8(h=hV%CpMXEQK}xLT&oAtV7Ifz`guvSkwy4IgiruXAcd z2B#Y(qME;tgDZ56^yZ9}A6HCV*uaZssS(GHXR}>yI=IR?mkGdL7U=lS#Y9;2#;P8=(X+V}$ z_%L(e$mx)5eb$;E`QTd^J(Ro5N)=Jp?pgJ?;1|d(R(9gF!2;ga)n~0%gdTV!izJa> zlD~wbt~1X=jq6U8gbiQ{WSMG*B#n7jh3~3CYHpVrT%SQ~L_@a88n@SGEmf;9>xs%b z_E}41*lIO*UsA;kxB1GJg~c%NE$ckc$^7m~`Qw1aFQibQa!*>3WC z-9G!YcGeoz5hFugN8$mz>SiH9%ZLsJF_An;x_8ZNm@32g6_ROEQRYI0(@3jv34>ln z*r(RrGo$l8)5TBZu8#ga!#5c+UeXJ0X9Ks~=2{}PU(@XsOHceyj9LLt1gGD(jj1j9 zDlaiesMu&^47p#d?w=O1BgJ=pDAL%WoHWdy)=__5XEY|r$nls>w*ub3-9p#iTF444 zNtw~#kId|o?j>ykP(cPVXlSm@y<_c-68evA^OUAbmeP#*|4NfVfJ?R{j4>Z{m29ogjS9-4gnruB;LOJOJdWmRcP*tZy? z6#Tj2+~z`_E`$|i$~CE`{&$ns9aU})Z3i^$P0g0HS zUf_ck^N=AO$&`Tf(?S*7WO_x71=6grlU@4tuaxu1FCct48>?8}N1muSpB_Od&mPNosp-(68Gi&1O0x^!NJV!hEse}JDe%5l z)i2SmwP);j$@ugiU{3N4qKc~Ym{feAWRF^hyhQSpn7pZdYLqzt za5su+DDDWjIh$Q+^9dtjV#NM-p!#^Zl{jsMy~Ez}ipMHg2bQOkv$TDR1`KpP_ORC{ zEF0oQt~BqEEX+rH_5G%Kji!nw5R3%Hue7v(plWZ-+xa-_c0?Tt#>o0heXL)$WAchf zeak;`N{u9WD%;^!jaoK0WM+^Cgi?`?eN$bavr^iWDZt`FG3Sb2$^fR5q&5|#A6K&0y6o4JsfMjueO8fU5FT-w0cgjsd0@5a)P!u|Ix7Tq_r+yJ(5y|j3* zr=aGMYp^#Wl>~mzd=SBLN5SZ#8J*kuQlboMhGTf=F+i@3igyC^M;b~Si_>Bc@R=Iw z-m+VaLQ9mSz)+Sq@3XO(x^P_TrWH9-F@If0g&sZ2R7K9I8at>$lx*i=3XJpKll-W* zOnOTx?p3g&>^v&4jMx$_iG%)e-6~&Bz(_r05^A*f&6`Y$Af>d4$ygdUXWLTq-xx-! z%NTNsDXc^+#!FZHVtR`*9hatSru&JLL>=K8>wuPPk1iZq~6Cx{1I*Sxq zGi1UBijh&@Dr1`%J*zc587IY8(J{wawhuIFJ3A+Ngi{bC&&U1;u)KN6DBzxJ6!t}w zl=_f*}IUKz4OiZ7#?VXr?iGzt+<+|)QQ7kQt;S=2R~valyAImc!pB+7EZ7C5-n6J%eLtWkyiExoAcs z0C@O#*ag*q;?@XJauuWt3xJjfZj&0mpw0FULb~t7@oE4J3?C^6E595+K2+;lh#ge( ztf^L;HY`P0&9A3r*za@O;BE(pdm{x(^}#RL`iH98?Tfa_pZ`@*jB@GSP>a`x5y|c> z)RCqRr#{oQ*V{QbFGX2beCTEENdBxRhS$yYuUn_i%kpg;pPH##?s@yM^?Os{un#Ex zs_#byvHL05!U?gFz1HN}lRsZ?a?(fuZQc#n z|MRiZf$zT7$iBM2klbJiZvBVL-Pf4V;=}Hc8buf!uG%MPw7OE*)V1htC-HAsBO{Lm zZ-VBi^K&5^`o-FU`sGs!Om)F}L-0Wr17H5U&x!8t(L|@$_Fxb{R?0DE&&e^}hOy~EE)+U1Sa+xuqOppG46@NSg7N*n~ zGWw?YU=A?bX1(8NCk%uEmA}vb)?#8;w3-T}=c`ZbW+p1|B5;QKHSbK`@+e;^!ahD! zK=vpkexap&{?ev?Y|ZtDU3*vZOW4b!`<-a%x0t$fQ`I^`qy4whHba>GQ}z7;b=ZG% zuP)hPEAQ&eRTqj)G4X97x**ZH;zl>V`rr1j0~{L{*68@+9T4hAM#xN&zf+ef?9nr! z9lqZjq=`Px{6OR8Br(-ZgiZbF=IbiD zQ@`xaj)lw2W5^Ht^N%#3qWP$TwAC+XW^MGg5ZH`Vc6(U5@nwcs&6sUvvgvGrG0v&^ z7Z>`41GQKQ0VpQd21G_M62*8x@^&exhBJg_1drBoB<)3(i8Vb`g}pOz9qXU*3hLOk z`r4<|sQkL9HhF0jbIPkwFd0|iH#b)&x&iDVT(wd+vFf){!y&(R50FS-hm1X?BC9Yt z%V0HKUe(bAORuJp!)9_?ijLGBEf-*q4IA6aEXsZgc*Fdbc950;>Q zgUTS;ed^6kpPlU+Gc8my#SVxGH83~P2@5WANas1oaP}>nV7fWG>;5FE#Qu+m6cR8a z82EM4%%j;fR?XL8PN<2okGkHHeIPgR10v1-2SVBYc+&e!-$MG2bUmVkCX5U?gq@y} zzFWwKWWNg57B7>Gkgfp7vT(!3O)0lM*ynl`2`vE2=pi9Eb>;j6AQ zO-=0ra3Nj|kgFMSL|N<>LZ`K$%Zz%QP4WDAk8csq-;%|G`qQU$*JihB;_1ndbp5z3 zIsX_c!8}rF8qE-YM`4s1BC(fj*qj79o!fE}KZC=c=Ci&udA_Aik+*Cd!7Rp>dGoVM zh;MmddSj4F7jZmIj*Vk%Fn5Avnsd`SHfNr^9-ED7@RuzNK|3=#+k3YUlkmuPK>zOT zp9Br9Vwx$3Cz&w?Z?$%UPO?KFqoB}|L=E0ctgb~RwM#~UfpXCeL75wq84 z9?J%)s&zkQ8`VBgXq9Cu3B$-l`>+>u^oga;e(229X^=b4eK#T15?|L#MJ=HQUbJsS zM00*3VJQg;GNs>bA3kmKysIIxSosgo$Io)0C=l7$b@EkP|33f-c8j*^ODPqd6sKaC z3}qhj33EcfIX13`l6`7x(`VLIYCbNP53xbor^YP&W*9fEYMlKKjs<|j6UdEe-uBk? zwOV-3yAxKfXpH^m5Hj=A@yr)2hy>*^6Uda_m9O_}zkz%Znda<(qrAWJG^H|d&u{oR zfBi@+`?nP(CpbqVtFaooZY?zBRA$Li(%Kv*9j?QfuAV9Tx5mysgaT#(vrkh5@h{>~ zpVpGi(!6At3cF7#@uf}_HdtG0e7P0~)&;T0a0IAXgjf2jUy(Kx-*AR~ORVq)zQvDR z{}~b}i9N|5rwciL(tpF=wI3aC55H#RQ%jEbF?~0;ybScD!i0J{=~Z zG%((xAHntZok=#(5!2aT4ZlRHCM|cL4Ap7GH5{!qHCOx#*^RmuN|Y0+>!P%TN0R&{ zlbNeFbwv+h#JCIf#4N(4;*S2iwsBuZ8aVP11JQnqN&|WZw#hQRCmtA1)cD zucv5`cVuUR1>Fhi6GZkSMc>=0F4qJg(-lMwacR7LiZpCOj}dxnf#eB779WqM6Z?*8 zO=`%tcHR6h3S3>=VImLnu>4rcN00NMi2486hP~$jdN2p6UfW>7m;(U-qp$ zymKCUfIQ8eHUb-wts(sw27k4$3*iBZe)$)F=$~Ew6R?(qhG^{Th8NbIq zqa z#SgRU`5kz=Qi@m_=akC51~OwsyvF{(IdqyEVtBbqo?yDdw(278yzVa=9Eo$P==rnK zjoqRL-@G2E*)*W-=YBkVyh!1(vazMcqdW|(7O@2WtQHIJI8tjdWX8GE-d6B{n z4td+8I0mJU3q}9qh8{Hfz8iJkc9mog+~Vn5y1q5JZyU z!pDiRwM}FU^9SqSx7fKDLFL1-g|pH!t`Q+*vyZbwj@CxsIKqv5l7wJ4%#k~f9){6i zMrKlG#DGMEVz_UF#-CJkUj&!>aeNG%u%s~6OK4NPd?fNIURvvQcuhtUSZ?5=frfm~ z3wkhsOOkfFMDJfnCxn;gEf_@Um!O;@lpPyDEfqA*Ki-#MFD%97&ZZbiN*atRxnaaBr<51+-) ziS8=AhUwsAiq{sC>An^7#B||mmS-#BMmB9W*Z3+XD1X%3=5mr*-t9P}jTT^L7d<>ii@^SUvkKJV`(D+L#1ZVpX zH{m`zX1_+8(4T?T(<6;7ofsJ;CIOxhXnwoY9NGFHdn3e?I0nrbd(hyA)S;W1CA}JG*j(oxbObO+PdlUMMy59V6>cw^GAk+ZDaTxVS|jL^KQCTn5Wq!=(KyJ7Iu;Q>RF zyh#wF@nLl#Cq|2aa0tplt$Ag88xhd0r@$`C;Z1N%1n3pd!)2 zjMJqIB;1bMAUI?AEcwA4g(capOWDM849J2tv-+>&x2uU9aX-jDIyb)`e5R6Dy0F+L zs3z85`3dGJQX?4;(GzS>Z4F1Z;I&U zP~|miUJj6d?a)IciuD*Qa*_kfKG5wDQ=$M;&=nOFH;CMl{O+r5AHI-ZlsOp9p{5uY z&AN7nWM$5c;)5U@<#vQ(ZoxyxxhR|CdI|U;%qHB3rfyFUylA&X!MqfM=n9yqe-$CCluVWOKH z5VK>U#P$yG$c?#>FsXy8B*PyDyL1?ueN`*3Kpixu+Pn8>27KPXm=_cjAKz%7lx%ik zq@NvV1WLk#?9YxRvz|i@>*$3n`>c|jAON+7gl}XTKu^bEao=h&E|O2YrRPL9C7B+i zYB>(EW^c0WEgcL4`xQKeWVd%=!nT2&Rm({>j4v|B{|5iYq-0LgRhTR_+*hnc_FKNV z#XPJqxqbNLc3Zr|lwdbq1rhyeJfAd2YhLESF(+IK8`3xqqnXJkO0xUR=favShGhUL zC6?hoYs=}p?CxDBPcv6lWKg1!`43R9qUK|h=+hvH_~~G`3SsbfYMUueCQob{$oyqA z%g|XwlWzZ2=F>&@nbEGT7Y+Ow$tL$gg)qvW8nb?XZF!Ok$8&;B4#93E3+#yp+EzkQn5$|%)`8yE(Rm}P61C7Lb?NBy{|^Qhu2wOPjjtZV;#ClYY5 zyz&XSY7t)jEz%BRpP7BORo$z&xZT^y8T`bX-{@>SE(lC&8!YMJrlS(G<2 zva07{p+CD;@hID-VNMuuBec*1o!%#@iDWN6@_M zS{W$&sJ(5ZdN}v?tSyrwhkcqgoDhdOxT)cE_S8D)FXvwf^snfPHQ&`Msvr5Pd(92l z4}g0#1JA{N zoG|dKEAaBuAC42EK0!iUwSS@kx7S&Yt3#(JjDp>s!8bn${)unXi~aG-{U2dx6&2M3 zZgIN1q-%x_Y3Ys`x*MgtQ;_b48DI$M?v(ECE(rliX+=Wve`np-`*0u6+gYd9cfP&% zZxdc>?_kVRFke1=8gTbO zbQ>w@F*-CNnWT^*gDOHjeVi5du+IHO!&QsS6SNi<+mI6NB)@!aaEeZB+Sh`vytFU- zH~#7IRR>^qbXDjrem>t4ou+E*weDG3sK#aCwb1`Wg=4q%eWlWHRLD#x`;CX?ecEbY zM%V!MNK0eyXZi%(TBKP9=eJxd+WgLIGittufoaML_M^dJSwyg8Cjfbl|6``69(dDt}4QSyOBua`>@Ve3*< zacUSjWZZ3uB*?==|1W3zdwe#WMbU_I>S%I`K)Y7Q>=X6(US5%Eth}_gwC@(0Ja3Z4 zu9Iec*cftHjY$#gIS67A>ta6Ue>gYot^G3`1myiY!D>d6rhIlG(bDw1ZZQ0Pe7JU> zm_E&bM_#G&gNHs3fL;x&cj{?w1QBMn^}J-f*<0W1p6$KM9=48MtDs&u~bn!aLcOnVshZI%krV#0q8vE2FCfW~ALAOGhq!hsX+h*Xp zezD0cFEmD#V|Aqx|<|y5$xop-ee5INsB9Liv5EXCd z7?2$BOO02K);mW%@)jESZzrEIXJ62kjSXE()*Yep*hp7Ysp=rjywq7E2Ql1mXEl<2 zN!Ynh^tGD916U;c2A`s-nsr-X>C?#nHnBR=%#Lt_7+%wco`Yl( zeq)MayGCLy)yspc!KRg+9&9l-pea}C04SlRp{I$f(xkx~Ih&iAhWCbskf4&%L1R#! zdUO&_rDq;x-4S;M8TdFc5-L+SMCd~TvblaR#P3Vjkc`53q_k-4G2msE+K#Oh1~`Zo z%cn-36PvlI!6CAQ(eqaKHSdar%N3aw(vIELK{Q=1ONCu_{(RU%MZc^zBVl!=&>gX6 zD>Nld^^*0S(&W=6avz0vT%ZZ9v-yJC+NEXozVW@(zxEKU;RS6|Hy9DMa| zpTeSUVR}Vtj@$wPD33+Cnq!wIIo+u?7#YmX1*Ftj@$`6W)dL{&eH+rV3av?#Rh09@ z@uW9GsW~IMqkCC3>8^SY^e3{@OApA19*8H~D@+9@ntb%ezbyul`SE7*qTE?@pN#7M z0OmEvYBrOy=(~S7le$!%aj}9D2_kkooNIfoeF=@ilcqO65NHw`WJt=?ZZG@OIjXyJ zK`Xqc#VALV+Q~P*yG4WeP@M>X3E{|29Dr<)7bH$^K-vVIT=dSIj(GU5M%C-!=}Z*@ z@=$4XTjuIn20Aj496?1KpR7=AZ+KDBR2QW|Y%}-Zf;m=jwmBr$(?%ncsy>idwb_U! zj_;TJr05e-z^Ymm+y`*TW;>YFD{^1`JJ~r5u<=#ARYJ~pvZDCR0Zq2*G(S2uI8eL8 z2fr=9#N+Kw$3}VV<3L@UPn>lcU%J-~;-Vv3;1n2%6JHD2`5yUcWol|Jg&>K>mto%Y zZd67fwA76d6#xWP+!nCR$_XW17_g5aBg@W>wg8a3kvsoG(9x8LZx^(X9nDBtiN0`oKe!nB=@(^aPC+`A`%tMuJ+^WCxabC0GbHw|T zr!n^(PKf=hHCIIMdDUXdi%q*zEYW0Spwp>Q3}<=d%5C)yOC5{WN8cvem1%c1IYBUx znQlmUkU7y6B)Yr>>#K^*DQ%W1^DXF~G$M>L3DA?QJa{erW9Z(iTGNgP>QRdE&YLK> zQeZWjLwc;kO&hA(0F@UK=POe9-vcerx?6cA5xgii!f2tqZNaUh?sM+|Gf@E5n<;^? zZ{ekHMeBgmonEEi7y4`4Trm2xh0q#`IU0POx7NjQNtU?NV8(-sD>cEYaJrc&@`yA6 zfVyPI?tSeQFpgMto(k;vrv{F1vFJSATYI^tPMb3CP>8XVuW%RJZ>m2`w$3fhIk6qD zH0MFKt=|9~^GyEegNta4{_@5$t3Y9#vP{d43-pMb8gWlA`uCxRnTlAR60r|eS9SX^ z!$eF#Cq)(-x$8Lc8LrHGfC+Q}xx0@t7Q=0<`R#cA zQ}0uValr04R;~6xqkt8*C@)hWWewxaPahX;UYij0;W70uOOsOU_rbc(HxAS-$w~1h zT$AsbuiJ#~e`~n^nVL(5t?1@yi;U|e@2p%ZI(d5#pwuzS9l%`pW4Uss#R%on(x% z3?u}pLk)4686rw??yl27J=&;?6iv7kE_|^c{FP3}dp(Yij^u$oa~pPw$SV=#yHdtY zbR;V`V6qhB(vUGV`O2Rp(>5j(bxF7zNgBF= zdNe$nYydzDn!7wL4hiWP(4Jg?-{6TeJltA$KZ zwq;`HG{J|}%ZJj41i_AQBUfrAN$aDOEMMebUdEgmMWd3qho;kBi=P|FJH$m@b!`f_ zvOUPB4j%chUu!b@_EntVv1`ay)kI{Ir$*DI5w+8wNX?N+rjhs~X$nd`xJP;pycKZ5 zEj-=NTa1-d$P;At0+(%IFpPP0Y&-M2X__n6)I3J?YyJO}nNf8_- z=mJVTwU)nzdFpmAJzw?a{ru=^0s>axfvBpu=j7^h}q(C_dy)b7vK!dZXL)K2{&n82bLlz3ZVOyDn3 zM$3&vrY~64@=9Z+R7_IR`M!|p!J$}R$2d{u`xgAY(grGBhE;GLL#jt9tn&DclKJW-ftCkL{j`AE{R?x5u zt~$}%LZH%~gim@k**;xkBMg@%Rg(6fb6prYyZkuS{}r-nP|ejUhjDl>>kSDsp|VEW zk$c^&9>=K=6-6Kxl*#b%YMTN8J{(P?s0RR{{T&mZ42o$e50a1M-oHxVaaEXV(G+3D z%TB=R6*$90X0f6@KAM##ds$Y5D%ux;`$M(XI>qPgOy8ee8^*#1*}Dj5v;|c6)h#35 zrO|MHWt1JVWYJ>SY5anB>L|wqskOiKAPsL$+$v5&+Kk0b_@*Y|rO`LvAs2El8HDAG zmtOgndUx{AB$FgDP(>N`P@WOw%KT1=wo!XEWQiV^Du0WxrI=%hQDQ}7IFLiv?zTBN z_jyg%k26}weZvk0DjI6SvYce{en5DpUUPhj+;H@h)t+ggmPGg8xj&P_LU*6v{1Yhr;bVT(w#RH=~fc8f7!VceG z`E!nPHA13iuIAWX>1Hge*uO#YnwmlaZ)@pBas?yN;Jz8=^D21W`9T>6@8XZ_xG1B! zyT?|USFVeQKOeePaz$Z-q=VMo3$u||$=u;L=@WzliL<(GCqm8MQRiKy5-qo{f>9py zzErmjLdJAn(dq%Igz;?PS!NFYZ>+`fME|e)j{v8YqqVlM-RAu<*zeSnEwt#S9Bt}ItTYvZf}RNx;wivOZ~F4 zf!2haf_1Un>p++YM7KQ8^x!MHxxXOAdcx+ysP8`XEU>+l7hK-7>3z4p=esR=f4{hqJIpjZ03>r2 zj7f^+D1BTuhIP3G&6_$*W53eq{yT4V%=wY4ow(jYCkbEG^m+eS z78epaC3%V}Oj;A(*HD@8XdzrCuN9l|9^7`?_T%ra1JEyKNx1W)m#vdwlC@fX!hwQBNEQ`IWV%%Tg){m5e`V*>b z(|ZCU6G4c6JNO)rpKTVLv4Cmyjx)^^xHkC+TgT{shIYiny&Tzm};Y`g+fxTfoUcCe{}3MqEyc7eJy;98ft z*Pfw0sodU5ZZbPBE*DMYY41czPJ5EJlOdXeg3At!V)NZ<0r5xRb9~00mEg={{twX$ zm(5S(f8y7DZ`52F`9>B)i!is&Hof7vs#)-#KYuwl$&ZribN4;HW_JO!b2zriR6!KIiQ18CBH9l4%v0eQc zmoW(i16Vwzv?OtvtBa>|}eYrwqNQ%sPLBM?S{txbwNOn2qcHN`IgzzbM^85Ku zt)eO$_=jT)oL?Db`zrS|)jcYf*Q*4DEcZ5wRlSCZZc0DHo6nVlnBsHo=(O}F;#E3H zI8avv6)RSA?*$9&e76;s_caJV5OPhnv+m&_cC#b4;6T4zINU!N7?7>A{~2|2Z$~m> zh&^4WyhXrU^`5aws?kv9Zk=h`^dv%xWwtD~dl}mDyAp*1Qau+(SrvV9f>y1x3au(7 zcOP@6iDcZDN6yJ*_szCGCMNYt`kr6wJGV~T&9MGN$%@PS%_ADhu9aJ&Jbmh!IjBlZ zt~Ie?Ng+>zzw79&&ugY6G2-MnLKnjbR#)*sNm;(HR#lq>>+^?Vnsxi_K(b-^+B?NL zQXAd2s5*vp&ovRHqhD|45>KyZzoYv1-xXOU7GcSzMJ}LgipP^U>3{sDV$W?H0>`36 z5CW9-<)!8{lDogGthad?8=8y*Yd8%kb3o~Enc|TC@$$ZQoexA!%uD3p;?Ux<971Yw zb=cG20ky#dkRU6LYa$@6GJz^al88NR!VHr?7UVYcstq~gJe+>1+d0tr2KyxtUWgzV zeEZi6(5Tzvqz_<7zoi)!i#CyyV&BA1=?>Sk{L4W@F`ohp!(Zmg`yIbG=v*WMM{j9{ z`XQ!hdu&#za|=owme$S)f|&&w-=}ocytgg&pz`B4iH=x+P)8w~?`q3b{_M^xZh`Ix zJ26_yC;rTWgp+@Wc-OgHZq@&$u*khO!enu$fXP3dzRd@m@?0wdvL#d&r_?;xEsEY+?xm znh#GpX}SjdyV%r9jGoe8o&TNcGY9F%Lq^%`viN}AaXbNZtt!5-N*r0%C8~xQq3vB| zo{jETR<-QkO5Rh8rLts@FKx0Nzk(wz3s5*nr*449O302}^mEY?u{Z2W**0y-$M4!r zd;;muZcv4%gIA_UoT{!|4EwSNG9dUB23DG)XZqnB;VeWdG|h1`xEND=cWWB5U#xb@ zjBtR_sS@cfrT+SKH3lJNra5AvmhnEEsY+{VcldP56ObZIp=2-v3RHd=ZDjD3 zWOM*dWG9=O3DJ^dRUiyD8;guljUUx3OV2mI4fVLpU3tgeG*Rg#;5(mnsF7hH7N!A3 zO+q9-R-nsc_Otu*md-T;D}#^#IlEcjYqE@@cjzC!s`10AC*77Slan4=BODY-r`TKd};DwE3&-o7t8 zhJPk^so*)#P3Df)jy#K#O+`hU*i%c;u9Tc=J}w$hC8Oqm{K#R{l}4|#sSyR47ZC5p z{K@7qxd`4mK!1pH9m?h+93aogN%QSMV1C-f11~eG)f$9G(L-0!`qBPGMJ2rrjaw(Me>NwW z+YA6E=a(MLiW5YgcRq2%=9r!B(&hW+IEX|l6OLBW@pM;{8Z3u#xac@#q;+Jb5GLVZ zsh~(q{>Cn!X`H%w6QX=F#;Xx`d37M3jW49gY{j!F{xbZFRk|_XgdD53Yj7xfW8O+% zTPS`t$H77t!jr=&rQ}1Z_EYH_=rLP&s$`&>bPMpx_JG$;SgXNcsA~G{ST2ucUK!c= zY-jKLEERcS{0^Pr#}Wx#{q{B`q4EXPqY)Wd^>R1nVh2hmf2J-d_!C27qI;r}OnkRL zBj=*2d_6ZeMq>LEwN+6e1;2Q+e6vG4YmNo0AZZ{Jj8@{qQ<*HHHd05A6KP{e`?m5J z=_FJH;j{y3do9L`E6?eECjYxHB$9ZCxJ2`Rpr6CkQ ztX^u`eFd0ut%ZOi%h9i8b3ch+e-N0b6t4jKi>|`LoLGx(!o>5(vke|s*IQ6(uc8EerXB2 z4+w1hUQ!u-#s!MmkIh2e-sE~zB!cRAEc}C5rf4~*1%zlRs*r!cgEEtMvX>QY(Ym>e z&;@G56oBMoA=fn4Mk%*0o)(i_V)W|X>t9`e?KoA(KiQgzV=+vj=w8?AIr~d8{`;Lvk}sgDn@JLx}bG5=cBMkslTLj%vWsY z@ytmJ+~h2>fxP9|l>nIV3=5=3>zi7Ik|)*VW}ch4t9AIMiU;XXo(W-guwr4d5 zA!Eu{uwnzv*A`q$%ibvb8PbSzz%a$Z*VlO1jP?H`FTY5tgoBo}{Tg(p(C;rF+cZiN zg~dP7W#ko+7*v%i%@i^TX7*E)ZcrFEMq}fIjCFa%B z)&CG&3mnUvzw%u~>@#Hs83)Vll&Uz1#ff-&DTW(H0_)XZgYQ1h9nBzq(w-=7MyG70 z7faM6vs*J}{;aQr1_4p$a&E}XfhQDPmX_b)(2^~jc0zDO&w!g zo*pl~f6FKmTuIw%QJv7&#zyF{tZ9=flNn*s_q05&at4(p%X}gMmIz2z8m)|CU8wue z7)ta9gqyeX@w8Fs$0||@uk0$fDfBE865XjeC%R+f-R^C=tjabrbk2 z6j}c;z~v9&_>2^W-DBEe!Gzqzw8gdHT?VO=dLvx5&TRS}-6Tet(l^OJA?sLxzm`@-(h6dn%OkR!UaG6UidRavV z;=4HtCk;yz2_Nc*rA8f%mS|qrAXq1N-mojATiSiNf^-vr&_Ah~ujq(7AM-WQ7$xQ2 zeJUdc|A)Xu&Q?kH`_F3J>GR&(5TsP6TBMlikb;nY5=ECx#e{R~2ptZ4KiGVJpMbB0kwZ2jFO^BhGp)iYjY`uUY_z8a^^re0R2 z6`U?#$_@d&F)5<727m>*i(ECBMojwFxV68WtCKgLEwc-U>2(-BPcTc|kGEnjmjG?O zJj6qP!UH;|ms)%%c-Mqh;TUrpuTq<_zpWrNp3MO=zXKQkc$&#>+Z?Ep>GaBQD9N(0 zx=g3!lxWxc3YvZAhdJAH;gbq5^MMi2j;i6QMTlBFKQQ^XQmi>$&Zn9cSNm_SV;aSv zk)G1z?*TTpKT0L0UeVZdo8nAWZ~{a=@ko67zH)YBJkcUu&B#?Rlr}35bzle1K&&TA zuj;=f>KXO8xaS%Aa>Sv(VFXrtHedisV9n!%Y&EEG%+Dj#A}WC z_jAcgvC&+Ss&_Y%trE-RwDb$+{l&cWbEQ{%kJV+iG5N%rpa;BFW6%%qBz|YYR}T0a zZ?5Q@tR4f*>kp`)PQvD}&|$|{lXk@$zI@h;&Qu;+BJ|)$(ZS2Q=vUQ4`VRrGT%K9D z49JQcQl{79?BSx-KV0^~jYDz49^GWfFv=p)=QqBamx*ab_$F(SqfrG&l&lK`^BfDJwL#!dm*hZX#Tm1qJ9DI_UM)V zV0D$q<+^8ty<11ENtiWeu|ec%gaTHL0Bljt~=P{k^}?1rvdf7KS8 zP$}7=m(@ZPtb{LRK>^q*X>;w@~MUy zttuixS@H=!smJQ|fXLJtfJrCsW4uxY=u}#S1h6eCudhgflGn78I(6-7rNb!3%35($ zmfllIPHuzwxQrx3e!-3!MAUvvrriPR<~fI4OY`YLRJ@89jrzRj{Ij+wE|*H$I2$J! z46sVT>;a!C?#qDpR60 z?b18!BKRx_heSWF>n;|6f8!#4JVQpS zSIy2LJIczkb9)igiqrtccKsI87CpQ)02%?ikC3Z*x|$^(q*hi9w=yX*RtPDU_}3y+ zA=9qfP(zzfpH%g%!&!@rlriy?>+z=?_aoR_oD2!BtGWst!x+J&xAG>)epH&xmLHXy zJz*d(VqT=rbL7;1mGTWFaW=%5^0cK;-qM1nDr*dVwB@J;mGPJHRviU4PD}IS52(tR zJj1VNwQFLUTN^e|Ffdq}Q&AP&tySn;A6eEaq zZWbCnkwITw)qUv#;-9t^J};q)kKl|<)9M9(g~QJI*Alnlbm34_IjReWSL6P#lHz*O zBA(rUnUc`>#8!W~DBSWWArSGd0!W1iwptYg_>?%Aj$J$%IG3nH(cMmszCDOpu{PNC z-Lie8A&PL{_hHwOmqYotYP3$omA+W5-Vzmfsyl#^G$4sE>M+U4C7Q;$w1vP=FgGQN zc>A_WUA9t!(c)HV&n?COaBkFUXDi~l+Ay%?B$b?x8j;CIF_aZH06aiw_gX0Mbi9lC z*=dtq*1b%n{3!!N!rc!T6oAz|bm5X1I{l7kmW?_(Wo_$aYUfK(O=hP(U zKbdeqV3ZU)+`)PD1g0)&dah{ZxV(Hm*p$xF4}wMFlHasS9bIr=6K2ThloUF$^!g4MhQ85FvJtmY3j7HhTBC{RSs zQn?}navC)iN_2CdF;aCD;jy%9P_YQPu)0y=J#Ct9iJ#CLiXWl8_NSS3hDUF19*r}} z;QUpMSVJki&XoL78@3`Moec9WB6PPhHxKq0{v{*W0nly~8T6AGK#vNFyF);GD+sDiPy)z8l<0kYa2C>_ z73X?Z3MNp-&zIXzp?ceumpOI4NkOL8kC{RD)9?5=e|JJeq6z;)fH!Am<(KtkRWB;c zv}I-$`vLjM@2zXJOwpAFh{z;6Y|Lbo{)U|7l#M(tY))5Gge}!j`2ElfhS9!OH;in) z3rtXdwpuz<`4aOPWb4E0;L(@*LCUOJcR%o#*0J~FHzWh(Cw-fjX3Q)3$d(#(+Ew=L zge-Z~2_hZ{!An!v`a|_!Z=ug=%4#%$jv@rgR7_@8Dp_ELmVUYKc65%HpEamqFa7vA z9zIMp#MJR&yeYK44eZSa;NcAjslG^!2q2!QghMvJLC4Y)z&fQj#MxE5cOfo=LhfNcF? z+&KI^3*+wg?tQDD)(yA57J-7}DLJ1Q_oL(&N{F7!ol=d-b$LbB8M@BTjRD{LN<8JX zQbs#uji;5=t$;Y7qkl6nvDtA6LPu(e=9Cxjd3#TTD+6?H8`Ui>Np8!5ji%>-pi#u>n9>pv_IrhTtNrh1?kR z8-)5)r`I75Bf}=j#zYT6uG3(dGqc1q;CTF^t9k{nQ4(6mUZ@%?yTaAW8UMln+bAQH z9#JND#+yt0aK7KHPyY*?sKNkD{Sgze3dym-s_f|nk{|~@KkJG0t70T4Q1X5+e+oKF zqRIMdOo(CCo}{&(Gy;Js?k+g8T|x*lMJdn(lWsKH1G-&=7%a!g7WfGJoc*!4b82r! zY}2RRrwgr9yAop>(c9ST0QQ|mX`cI=pd2CX?*j&=)DO%pc2fJbgT^-WW0k&UH%gZ| zEBgkECXK|>b08v$mYS6>c3TyjRmoqPfog8z>ONdO@qEL58jeFI`Sr|8Y9)O4db;@< zD#WzfXku1?*BS`)r}4eNJ}GbEDl__2q&MBQnUi!&-Km~J6q+E}tzF0bQ_y)r7+0pni4QgBhl1oeS<$ zwwkOzyChudntkC4vy6FhJS$Q>YateEaVT7MM)<#nxTSGg2=umD2L{TX zlT`}XtNBsKTFKf{Y>#^Oy@-9Q%uNv zK+Dhn5Mo`gxIgX_|AsN6pkgBznp+`2mRcEl+EhOO{NP&$e}?u}>lMyEup`|)`pxI; zchIbG@ooNKFkA>WW#C&z-L^h)4eyHZvi|)dpDQfqDZv6}5V&UzygSdih}<$M;2-(@ zC-PDE(@@{LFK-ej2E*mziimFSg~r4b`A5-0l}`dMJkr)Z*}X!LO=tRverMA&EA~g= zjoV;#0DS;~@LPHwrC~nJ0Mn0x$f}W4@F92Nh$kLME!6@Wh;YQAre;%(2?^WwnE7GB z9l&r=1LZVhfzO^l58gZSE#AKby$N|&xBpc{o$7AQHCE#KtrYe!e_L%Tb4}#$q9N#V z(5~i=M4UBl?v?jsC#a7nyzt|X9JP}dP8rF_Us}tsR3nc>ocza2V?N{FOnj)yR|RH9 z9Bt*9S+Zfp$L+MU)1W+4OWv|R!C1ibSuZk)?E(}%;6cKY-- zU&zLI@cRRDKJr^748iz$^W{hQkc*C2_$gVpS$F##n}cn&$99q>c0&NBrd?*6Gw}>L zsC>cN2xTA!z2som^75W|5EoOvZt~S4#8lBlWo`tyz@joWI3wZnI)O>a2VUhW$S|mj z3?8ERMuKyN=EqV7oi8h`S0T|hc^X6kr?QJtzn)@AM?@s+w`?~KRp3?-XIx9lbWs?R_u zvk@NwRUa0))?tnQ+{eBzLM1=^6(8qEI z3+;As>CbWJJ)Fbj3=0;${$?kqUl6QskZl#rnfM^7_mG@Pn32oJ^BX8DG7fKK>cDkg zEb#E|^wPk>XtaCFGm;pF%H&b^{cb=E6#=*H&su$fT(XD3?7`z}L+`AS%XhWnVA&4S9(IXdwC2^3y)O*m|%9)U}vP~ z291bJc0qAi--mO!i^!@cc{Z|%X}UH()~Ykul6c`dDcSc~ugdy}X;by(m~EQU23Un7 za6wMLK>bwhrc>rhk{)c+j&L&oKmUhm&!X+9dH?g#6JtziPa5Yv_*Z8$312ipfvt71-nNugZn_5&TfU!yqZ#M)%2bZ}?Z zb<`I?0KoCoHB0-`+a0rQB(*%(G53;Dq3k4W8N2s^WypF%Km|XrfvOOaRh{{jLH&!h zEo^mk#PaOus2$5+bIgQA(nLZjUJ;Xip~(?99GO~@KktvU8oJn<{ctV&fwwb_Qq?S{ z5YAG2k^Z*pQd*2`K<)#{LJre>IInSyFU8CvgHJd_t`|JkqzS3va}q^~^0rXS|3?j+3H2gQ~UO z@v#oEMUCkPJr$k#{R%oG0>sZS;%Tj(vhqn+(Z~M;C$j;Mh5)ip7?WbTwqHFJS|cXC z_%NhV=+Ofq?Q3_!tc!^ zX)_E~iZXp5$>42Ln;^hM;9-%uI_t~ z5UGzj9g*t}#&mcsEGmdTta09yk|2+SYxc06(vnvJtqL3L-*`gF){zU`5BTLb)q*3Az+judmT46EbW;5(a%&9SLg<$Z&Z{u%(yp+ zjdV)?i}yfagGi8{aoej4Go72d9qKp{zfZ*x7d2pC3#{7lcA<^VD!0Ana2aep!5pUU&p;>r%v3EHzVA`4XKr?bV==ymqv&~ z6$EXFPPu5i+fcrl7l3Z4-d2#;f8I_~ho%;=dK4K>I9^6;Rr zbI5Wy7QU?-gX3fS*zJ!A2D+|MpkrN(y!FI1!+K<#Ez#K1(eDj$JKY*`KqxI6Guy4^ zvvtMj>Vb&+p&hfcVg4<#D{VL)fwer29ceIVx-C(usAfd8o`hc&3#d;>4vRIKe7v;3 zZ1QrsPE_qZH9mEtczkF_;!kNBG`z#tUuc6wFB;`Pe_#b$Nj}jv(FL?2VUI`sz*c&U zR9IZ6y1Uke-5dTac++QQQq4}I=cSXC%+RZVU7_2VMMPOqQsXvDi=0ZE#h5lltxvlj zErI*&SBOjT+t^E+!N9-j7O|56#~>y1kgv3ALNhT2@dhf1lQovX>H~CT60z^aOG)UR z;aw+}@kkD2()Tsk7rIRaN@YROVyoDrup5WRDi4C(QwI{H98ucG$tS%X-6c!bBj=vr z%3$rP?TC|#yV_ISYJnuzr9nalX+9!Pv1?gSp)d50wgM5*ViiTn27#JLomXM1F0tbn zeWh3cs=&E%CaP`9+2%XxA@%;J6;MRxF?rmX_WSOr42Lp<5%eIF)y4)A$~h5`=v^e> zWMb>V*)oaIsfiLn;jPI#gWkVX(gD|Dg~3R^C?)u*pWQ4SBj0I)g_A{aw1F|l zofX>tfOaQw9W|gLfPV<=qf@4Qf1X=VhEcSRbARskyE60LLXCLLt@#1h-KjQbf|GMg zP2Ea~Eoh9lp<%twAk)w%Xx2Uy($H7$I6N#FdPSQi#DB7a*8~Z8k zZU##VQtq){;6ydo{M2}T{&7u|gBKaIT@rpJBoz(|K?x&k{zo}9-HE;JB8$PqU13L9 zC7FBY@x7pp-7r!i&pP}2O)?L+lbt z8QQg^-nKZ?3^2O*l*_XjBlGIetX40i1KMh*Y^|D7asPNYLvnV4nd z4A&vRl1(}OBkiC7Yc=`2VG)<%!!>ugVyh8aT`K@pwDwrd!mZI5Hr)$AO(Q*ZnLh<(m;!ANAZ>=&$)))3 z+<3PSXkzA8pJI8T-olj80d!lWrXc^e+NnDBT!Gjvo;SykJl|Ap;%jn5gEd zi@3TSAK1C+=VH)v!%~j1Nex9lknFNX**>u9nf5lyXoq!~qg-4-sq^~2kmmQ9HyCw} zQGP9cOBhfzs{}WmfNqpOL7Ui7T*p~F)J11F>T!*3Nk3+lC{N5)`p5KA^mho`l z-!db&Hux6R&;8*sfe`i|f?18E53i_ve6;&mR)LIQ3TzB(;iz71yA%vQS)!u94KQ9m}aDZ`FQa-Sn=ALDqAm|zZ>|YJp z_vq=WONBNoVoR7~C=CFbR(m>BADd9(0qHi8i9#s8B*?CHOsTOqO|T$dOi7uOr{fv{ zCl7xQCkjJ~=qR@Zj9v8Y{aO)~L@-lNLp0%p-!1V19Il_NO;KVHw4H&vuHOE!bU(7V zZX$U}d}Q+&0racF+H^1KK}^r@mXx&|-HwA=+pU();wDGWR^Go{Hwl}BGWb))ZRv5Q z{1Al1`4hnRb?y!=L6*ZB|134#-M#7GC_k+DAS>$Yl6=k+T}9w)wLh&&9JYW8H6)RY z1tEWxvMrkyeVUhHax67zDq9>#~acE?tpDcK` zH-<5WhNtnY>%y9Wq$ZH!x)TkwzFa4@<)tZ>%OKq5u!*zuz37-#qLx2PETo6(7A$Fa zU1}-DgK=bl5P=F670Odzt5Wh8{D4T4-Z5p)_-tIDf+BjvdO!tyKbbFrUalrj@^`5?VeBIkS8Y4t+*M_hERRt#%-wJ7%Y`GiE>RR4MV!DM zI1%tgdz>{6Io4aN)gkgja(?LN&0^Ji+{6WVxFnzqvG6$+PVHH0(~nm1?a0{J%3@@Yrr0J-xz;k7=inFD zNsqT1iB`*wQH9q_QNy#NqvgU20U5n^67y{QgsUNI)0G; z^o0_>6D)31oI*SwyiiIytLj`9Nl{TusWJ7$smWh;L9|`Ug7IPC(iI|q$A3PqR=noS z%@vQYH$($iDq^sZ!g0mxeQ!*kQL39OBTzHt+;Ul8b>qB%?ePoELxdhh>u!~0IoffL z>Lm0>u(^|or)y2I&V_P)gv(iaSKg(nrnOALwwP>6@vd(+5ohn;;0Y^n{odZ8@;IYb z285~8Rl+Xn5T06cnd9nQR56&vYslYOqxQ7iwAg)&!lqQT?G!f@ZO+7w709aLBw(zu ztUAydJ(Hg<^`B?R@-C_64psXCFljvqNrqK)yiGLdEc~PCH`tJhA`}e)OyWugjyc_> zK^%ANHE(P3EKRc3D%u{Wf?DmCJW8n=D6jl(X3khYx$zZemjk>=ixQ_>5(`i2nk7(w z371@54yKN$IDn%cg}aVR*!yP|56lC7>y;ug_2<<5<1+Ln$g%d4WYhzE-ddNw2a09q z?HF-0N0RYgAVZ}LoZXh%l^|Rc?fzJ0)0M3rB=ecn1Imbd|3pqNHI)x?rU_RODCBJh zSOzOo@#C;P7>BVM>JES>G@^Nvldo0GU6bfJ#-z^V9%Z3_bD)vL>VF7vcoXa~h6}I6 z_S4b)L`(SS$x(fg&y{~I1Lkfp^WTHUHsN}K|BOoLXja~e&nN=028A8POL;?dgdHjA z{^Fa^+js=t*YqR&tgb89osPeu=Nm{YBnfs9=j-^dNjKt4RJ> z^i`vlThMn_K}dhchA<-?r-a{%_PBQLe+WD4z9!%Q@6#QU(zVecqg!%pbTe|40g2H$8U#c^x>G{BQ(6%H{Pw*A*L4T>$N6~dJkIy~c)gy_t*kfS3N4HvBf-tS&!T^_qOUN0*&Jk7!j+8u>DphWcv#e zpWzw4LaWlg3**g5a7shH_&@;Hpq^`aeSHEj|X5k7U| zMj&0c$9$-l1uF?hAy4|%Fnhe(SpEaI*O9(8_?Ojq!UQ%xW-j=r|t;w90^tw=ORl_S&&;y7p6D^K!_bu!0`FMA;yjg5@|V`8LIF6 zUqtT)uF?u7L(=?hn3dnl^)$W(W(W16>Wn)F-@aQ%PG}qQ*Yd^@G z!^Jmo%!C^q50Fq#vS0jpn=zyzfEqQw<%wwc!Q4fPGP{q@HZBAwD)IpmeUbOv9RTs9 zOqN1kUU!Aj*>xSJy%m!VbFriwqL62D?yi*4>n{gg7s3Xs~oc1@ln5ZWOg*9z`DwU_Ml3^sUrMF)M<`4MTx6j$P8B(GUbl; zOCm)k1OhKbp`sFHzQWI0E5yrea9p{9fjox>+MuX>f4!g~osOzgk9piEbv{W?7qHj3 z%8z&YV-D-DRF|UMqJKb1$F*gKs`4K_dMqGUa{H~@I_l(Y3E1O&wL^NhIghC>-4~wkP%7>lpW+>rsKa+M)N%gTGmOVTI;LScVsHu zqw~trU}iM>hgRO#y=G_kKP(G6ZeSpx3Lq6?>S3Xqlydt8sFzc|4@|G(vrzOFEx2B< zvi3fd4oXCSuHC>B+euNA%4pL(ST71yhRZ>Umxp2Ap`S4B=q4Pu1Jz}YVg2r065Hw? zljWd%y1wf_^An!On3Q~Ax}u!0U6xz9z+Z~v5!Qc|~< zmhNR*?MxcI-vHO~YCHcFL$EtAYAQ|EsjqgG%YMr7{sq=e?LJxji~I%vhY; zKSW=cy9PGF#R2v?xlNnV{)mX2$~#It5U=+Dgr;j3t(OBQa5>6;LXJi}z**F(;(&99jB;Q*$}Z79|pu><#E5Lj$K)sx_TwzEhpp?cECD zL3BxteI!!cjO^Ab-#B>)lDwh3GTY5R75juO&(}S|5BTh2qo`1jiI0v5SSI>RCJCX4 z6=G)P`3naM7pU(ZZ8;OHy2grBkjbA9@S2^^V~r{&bLqF8`Y2SZSBA!y>H3(D9H9>U zBqFtts$?pz0=nx93sRS1L$c+5_-^|qoyM&XOIk*t3w+TH^#siv<>Va}S^x44oCHX>S0N5JpEaCJm_BdA_ zbx(@9aY8aEDT$ngKR00z-06bQr*K&BCs#nmtZ*UxDHs zR$aV;zu%)WnsChm^}3V~WFlxuHSZ(beG#rcA)@&@erY7W%pa$h41-3zzQ&oo)!$r) zFv~>BoDs#UjUJ19v_U(XuFL*|fnKmzoex!+b*u(gS|4uNrSa03S;`o~tNTkVO50LQ z%88GT`PjBpx)aRE5-Q&IV5o!O>HMoYt1$Eyiegl!f~=_0ffe-2rQBG}oM`W(J6WQM z(7wdo5oLy8rAU4NOGFEXg%Y72yitGTcfxcIq_b?w`DtanyLnIu-rnO?C6trcm)X zQb@0|@6R*`eS9)FgRH0^;y_$uTS-R!#NhEO=1`cs#KxoX@_fG+2UVxy1B=gW(|!jD z3cH9@v4Bq{HEh3nGRgJZvr9r~kY)BTtN^uLGdNyB;E&6mK@^cC0!^Fz+KAVnhcssMo+2QUzV~;V*$B+z7e)NFPHA>G#7qb z`o01~K`5`kU$?@-ugxSWEQW=+0z#eBNh)8Hkf(fHG<3gt_(oT#{F{?aXUwPSZzwN)KvuC^pZjxcm z#jgO2IaRT)BNms(c)Rn&oD~h?K3K9}Uc58gC{@iGe0L`y+-{k^q*4{z47z zg%EC>tCRDMFFSpMJLyei28hqQ2?GFG%pTof*)^T<18#X&W(&Vpss+tA`Fgw2YCnin zXf1J5=KI;CZ*Gr_^%JFzT~A65al+=;KCqZpVcN(n_~N--y(>7D%d3${m{wU#Af>^J z`v3CUpKqmO7)SBg&Q=wFH%qFQS&Z98aO1C(EfzzEG!o>3q$>BlCGa_FZA}iNO#~j_ z6A;R~I+yG-kxpnF!cTQb)foLuV$|s8NfuVAv)Bd!v4aD_)pkat$;J^ss_WZEFp_L; z75K*dNRk(rpObgy9V<8a!AM!i`RiV{ce$HjSg}uI{J0H0H9-wv@_Rsmd#wSCQ0Zp16D={*%k^DBWq+lwesk%#m7_GyK zv=*fXB^56{izW2m!q~6G#9Ssi&)i+y(l@W|P-)=W#dP^@y~dPZ{NI!b%SK2(RADoE zSquBu{_02yo85bxUWQU@bKz2Te6vT)0YpL`up_>_2$_jsB&W!dH9sdwU#?+?RlX-h zaegVPg?8+k(kdB9W)@2v?X7#wE9T@4?;u!fd? z9U;jMMK`8{uCIiaYs?s9wyvL@bU38Wo(qGlO`+^SR*k8lmD>?>6Q_TxAo!6PDZXZqGyGE^!%SLDJhXJ`%&zI!j_E>K*x;s zbe<&iww6j$B^Hdnd|t8SdNrRndd2ug)ilVjTbm+pYn5cW>Osq0mQr!ZeW9McfBSRe z`+nGVnR$jWw|KL)I@-Tk?s6P)*V8jo166|zC&e}EE>m<0AUB|NzeCi%vr`tXuhEymUs5{v#>5ue88UJ5k~}~| zVYmE|qW?mcwwipX^Jof2vd#TCcP)aQq!@CNSRsrzwT0BpPei%_;Z8n*NFJ67Vf+Uq zko+pDz2KxWVYxWU4gv7t9LR5R0nP)Iu098D)QegwNmd>B7$m(G6nYYBse2nu*kN_u z5+^1$%g+u^BgnW_C`DPf0ebm3x8atrSUgVMVWU8Zbd8WHHlOcveA!PNS?4ZqhKQq| zZ7-=Xi;P3f-(d+GEA=y-N}?|)bib)ycDvxtSCxV!-;$@vV$R9C{`Hr--K%;yst671 z*`Me{(AAyQog)*R;w(xDjbjj*r0>$Bu00ADcpCT3)v4$j)}K?f{_I9pzLa2uZUBkK z5c`Y$X=JUjKyxd2Cc;=Y=|O5pXQ3_t$7i*to&4RFa37tD4erZIvR$HO*WmPyxyzIF z9?jD$`IB)BRi2V9>c=eI*@ztc`^uf%f)%NSeW*R=tlFe$Z&*S~XoWxK_*Y5xBwiX=cj?R`-?$7F?}9I0lEQnsKz%g}o+kFk5p~CXen&;n zKNeW787#I>`!qN&!*q+y?CPI|-VDDuAeDx4{`9#y(GEmOJVBuOkYX&18RL2(_1X#(OY3)jk7Z*?l2Ysc#Y=`Sxj$e#+q)5)%6BB`hlx23L z=()<_pG50OrOI+^=O<6RfZAn%q!1(4nN)=Zk*}N+-^>fGf%&q9FhemP?A0g9AE;rxez8&0$tR`lt<8Z-_#061Ax4a+t@ zh8lm0OI4;2Wz)mPQn30DqX%Pc2L8Zo`{!f?rx-PLVcSMsa1nnaPIyI0kO!!SJB;RLV@} z1E>EUs%V{)`&ykJhR4#|=mRGoDh*VfO~g|zgePB@{0*HZmYWsu?tkxyj}OI{2J}$X zveM0{NTpwkQR^w;VCK2kvzcWq=Vk)QV%JX1f+v5k*X2&X;+&4>WbBIh5qnPN3{_jC zA0-(rQTdR@oNz$hJ@cHRkkR*7tvwY#iLjQxbeW|gW1oKODk(+!+$;g-mof`K#x>mB zllyILt^}s@c%^i9rc8Gc)AnAFq7+oSSEfE6z_toZH{nR% zwii|r{O0VAHyC(*Q*Lb~`jzi$JelDITgOf2Uo*6iPFK!XbNu5%nXw5)7J!+Dc!rq- zNsUm$5UZ~ycr@bDn_FvXC)KU#Bsv=-qL%X zzxygogxRc*h^5>ceFC;@!-dpYq=N`?&hl7y44Ew;Yh%N!%&24cD{KE?oagdG+-^Qa z63xHdBBAeg+P@ME+Hr%ZiitD|hAL0_(pLtX;Z#OIPlzSec0H~G2^5>=tX%nVjJ$h|4zFfWk)@%guJZ&JsYWaC+ax6lQNB$bGj!rrzb zS>eFE`^TDs5S%uOgaig(r883 zwFOp>H!>CAI@;DK+A02hPbgHkiIDwz>9fjftUQjVL@)E9M|i?3)J<@Hrd>iyoo??v zEICo=r?v{aq5EZ1Jb?d1C}*#r(Qo75Ez>>Km|%r?es_m9n->FV>xF#hWfip_wT) z3cjr^?g1w|~4aX(0kt}-(_kE9Fu zey|lF4Gga&sNZqa%(P1vw&!hI5f;Jkkgf?+*b#+90RF+=1lx+1sUKrcE?CfQW^z5p z&uZv^hk!$(Qd$N4WU4TCQhcH2qN{`Ic~~>(8m!m{9aK6$Y!TmaXvwQbajml~=MgJk zCTGfa1q}lg&*1tKb}*OcPdhsnl!I(1Gb%s8fX|amN6#B{o3hX9I0?fXnR&M*+zdrT zc0ptpkLpPaFsxD+?-#4o^jp&1%f|BPU;S8`EX6EZu-?D%HC*CtF^WZF*tSnNEdOpvOrC}W%rXRzq#3|r z)6qW@dRpGI(5lCQI7Ed1_*b26J183svD6`Fl^eOsjPa0&g1q<{jp{3lkGj&XFt^xOYr_)~yS3M0xYi z51jC`F|3t>)=2+UmRlej-g|hkw2e`_{^rF>f+q7Hbj^WgO6&US-t zs{M6HEEFjG6QOzmA}|l+D-hu^%cHlL-`jSvkM(AtmK`K~h~?8wVL{1$h~`@2orNl3 z$X6%X44PDK6EeUYTXbJkDYZ0o>-dDCP~R|LeK(jG*u53zUzcg^cXFTx5sGEM*<^DF-Y!< zZL+ALj_)~1={z)2JMd4SznZ#4$05_=9E1JyVMdiHj*bpcyJIYWmi-JK`_5oy8g{0E zIfP~)U9rSzw6YMbct`ufvs+=m-un6U*k>sC9B;B>{%&Bo3|1mymR?8VS_d*yUuyr} zCyCj~>CZf+I(sscM#?x4OT^TB^$jx!jnH{zo}ED{_F5}Sgm5@w)o{R>y-?z{k*{k2 zTjhq&yPNOw&sujKs4^mH$iTpw>D zEOQ*b(mdpLaL}ThhhEP5@*?aJQJT?)q34nx&`**sTFUEb%I_UM>0`VXFNC&1=B${g?MwDQRrX*4zN{4G z^mt@)se`f*f$!snN*TN*zA}dziKvf%jwB1Va?yEwfE1Nim}JB12O{ zvI{1NqFvB7Gpv+v)t(`V80u^P7R+=m{a6&Ep^_$QxE~sa zG+KYv!Q3Q;_VGj@6w+@M)YErDY|Si8omUZ)s#dLsmW;cFkq?#gGg42o7n`8DT>l1| zqD-Gg2eyN?xe8JWm;ETtU{h6B9ki{Dz}Cd^sXb`0h-P^8oNrsm!|8XS!G zoqV9*CSkRp@b*+71nXBU1sz2H|Gh4*2BxjOiMwApZ#cb;!3Qu0keK{p10C%>=qCL=dERQ7A&?t1ou2}0+Q8K94+=4 zGbMyn-)qjcS7E*+7!kM;`jGpaq_(p5h)WLv?k^w5l~^I%$}rC0jJdjgOZ&uShLNVf6W=!t&mH*s{@`pllW;1k3Y?e$q|<%a`e- z?8(>A#WRLS>6kpWx&;%(Zj?Ps`SN6RcRE))^DO}Zj$%|?USj?`A+h(5Dr@Ah9pAv# zcl_dX5|8l$!B+-wo{0NM9~t9nBg_2a95OC5e^_|80|7ZNRJC;61~?!G3?$4c98 zERAzpE17}s4tpU#ylYVju7===s~fCKU{1Dd7zF8^a|<<1TUP?K+I8D;8iP{(5{AXMB&K=ZpWA1f z5X_f#4*JJ&*h%oG3i=`IM^mK0_R3Ge#=IN94{WZ<*Bt;Uz=btD5G@@bgX)*r`oj>@ zpQ91Uyh7M0hx2O5i}kWX8;8zU?xs2)tZK)fSA^YhRE0qbJl1rM{4nN!t#Yx-$8oy{ zO#VG)oV2FP9J=$&#uGWZ@_&vCPo*Sa{Ow(^kR8dWHCQD# zAr}Z+B}3la|Is5znQnxysPJp74KiLl`rSI+knO4C?wX{-u|(%RTfMs(a3?M} zOPj!J1~$#X>J1^%9ZMlRpd6C>9v$8DAzzO|&5LFX=)?Cps>(P8bMjBb4PuSa)S)Ae zxG=8cNMp}{-a=ahf}|^y&ml3QvMLIur!RCN(3>OudaVd$a>l72G%p|^L7n2g{av*W z$K(VEl6>NTpRCs3{BXn+vM}G#k9=>mI~?}6=V*H$cC-&r%ZDPpzG&&uc+$}1<~NHr z8WUAahs_mO>|DRH=D~Z(Z);%rQ@kaWqLZlvnXCcDZ&m$;^h_K54?}7`NrnN7=c^Tz zu?h0o4`b+DXFk#OlAf3**T~h;Z~l)VQdGWKz0gtT65f>1OoTIA!d~3Qci^}ZIi9it zT`{QN#q5=QT_(y(reB_6Sx&G(2mH4X$ZJ16hWX|eLE@GSB&w3g*-}s97e!!wM@{t* za(5;V)Y@g1CVkIzpkoV~Fvv5>Pj459r2aG?_Sek=|o%SxNh~WWPGjff8#J z>UH%W#z9`4D53uTO&oGe0`n%6VrNaSsfiy_Z?W7s8rP7lqa^jRVgj-zHg0vrnI}%~ zbbZktf*4$p=AaaCwTd=>bq|fW08YLuOhl`vchB{wI!^^PAmsR^kZnf%y_5|iW7h< z;anbNLph`0HJFBX8(RZC$Xql{I~ui`tV$`rYS7?fO1~DsUvHJN$*nDFk3xC`a~kb7 zrF38yJuY%cD8ZRlJ`2%-81f}{(epy=T!LnQffkRa4Uf@grj4lae%-`6x46E{<^)%p zGMx)wm#`(E)wpoQGGNjCQsi0JFyBruxz?Y6>bo!TNqG)AK#mP!KqIzJh>erq(szv~ zTVg2A7|lRR0C=VY8^6?M2d}{;zqj5el*EDGy^2k*_*>4r!^ex2x{h#)M!jF3=@!5u zWD=~d)lZ%)Qq7*!$3A7L+Ts09KI=^{I-;HnO`3Bol~aAvk@gtYjKdR8`vx(&N9 zeK^>W+p}`DZIe9dIMaGhxnhQyp)NW&P+L`F_RkPKTe#d=xmY;;9!h}Fi;aj-diyHU z4JsYYiHA!ix7HdsM;Iltz0dL{LP-8d`hAg5pEPql1WpR>(8ygiZsKvi9TdI#1iZO! z?~!0su+pgEcrV+`GmL+-(t5$UH8&>laWR=DBa-?k$o+r?qwnu=Mi!+yusX^-;lz1l zuu%r{wz6!xD?hJFRC?6Hnx3OGO`Rj!d{CL}@k7#LaviFp0}T0^wdwvxM3`AoUg=2f z3tmT%LzEvr(9c3U{;ER0`CsN-1j}ZV_mKy8mYegg`t)?{sPkFsTX9~JTpH(F+JkrM z{by$28ZUYWHngN1|I1==hrn)aH9g?=7<=#sP1C*GrNIYyJ<)g{&?VEou;`}m--3=y z65r*()1G?+C|0I0v-oSc*=m@@5Y_~eizHUzVh*ceW4ZQ{UzLRd2?=)feYd(^)#6OA zN``5Jmn-wW zf&jWGDkO|EoQV1{$o{yD=W4n7o7ZDh5| zI`JkWaz{Sss4WCrn>O)Rxw=BS5LP5u-NUG4y{W2Hz&u535>Bwc=#N@UlS#gpKcf78 zeklemn+fp5ilx7>fd3f7)~fFAtcrwBgi(R87W=&{q&NHu;tjRVe^6Q~ zbNT2%(7-nC?){x?tNSl6!h*-+CtTx^s{OTm4OhzHuBN7_Lz}G^=eUO`L8hK2fX3kt z&~RbwXq+K){h(uM`245kxoqA_Z?pG4zgt-4?DM5Um<{dP4qbH^ctdp-Dx9d5oH2`5 zVE$D^SMgIh0#*<0e_DurRp;Dp1`t))HvGA%z4l^QuDMB-tvO{y*Xw(Ymn^Bjcj$Ep zc8okk{xOo%nGF7fn0YMU(Abetyy@6rb>DCHIS$;J&{pdqbMA|#{;+j9j;EUY6(4eJ zQe!0}rl`q(m5*g8Ex0OPi&uFw!cTeZKUgZ8vi`r9D+#>hn{_dMp5aa5#_Lv_nd6;; zp3sL6q+5rJSH%SZgX+AD_|rN%>gzqazov+ESe-rdvrHHeLS{y*9xtL$_u~6upiaLD zxguwxxK6_?*pk$xwe=^a(BS#O8k!o2ieFB$+Y)Je!UmQ8LZK-Vmj0so`TB=olC(*V z6zN6Cy^iP9XDv#d*G$vdkPS`-{@Elk)#`KiA4ZZOcUvy)x~%igZEcrsRp{Oq z^s@Ll{S){_q4b$2kYc5O0={iS%)=~p@bBNk(q!dB!=%+uxDx=Z-=ugFk3Hoigx5E8UbsPs*|GS&J5O6VR$BqWIKci0#=Q& z!gDhrjb;K&IXH3`{IbHaJ#j6%G<@#6<;h)h$QQ5<=Bw_1hPUPdoK;Y<^*m$yOo_ok~ZF$fBu7Y z>FtrRFBtWmvqo@3B#UpRT@XzL6Yi7lJB%oAI^_;_AQbPEmaD@jWf@E=P%lMp%-(hFC4Q*_6{|L>5L>@>LY(T+> z>Ye45P$Frw1s2FV(ZQkFzBl-Fj)LDTR;3Yp()_Vw^(AIc9YfO#ZZ(HH=1`*aYCLTc zvntu$n`&uhc;9+l-tk$(HbK;Zs<(wf01bY7B!9|Ix5+DZB9*En3J|9M)6PSb0$=E| z#eLialNXc340wEnt}-p^=3?W*DnHh;1TK_M^4T_ZkL_%($H)F-2&?W_Z+PNDWzhoHqqZ?;-ZOHIv61~=b} z(Z!P^J&D!Mm8ub2^5>&0%$qM^m}tP?kj&cBQq&_@{0i_1MdHRJ0k% zZod$4SJozr{wq!>!|LYCxXsVa&fJ&_6nUJ5T04_{OF~(5=4rz#_CBnD6Ht8mLI`SGD%YdN@pRq5~?*CsJ9r1w7z zj;a1KoJNM`A4V@Rc>w_WI$6_C^Z@A^R&st`@u|_=vydZob#RFHPhL2xJT6ayq5aXM z+vE{;D`ENFq(hDV+GMX(Jac4D#8YyTH<5~FlFuy(OX)=0GMPt-=96Kaqy>$YZ89f+ zTO0QTr{19@TF9=f{QoS<6L#iceVlNV_0TRUA3vfk$^4v`8}2D;q~DVFh2$eqlcT6H zy11+lP7_|x2^Q~xjMeAa&dV4VmQz4w*}~&4OzWm6VYsMwv&EKnAGa%FVsTvgbdh#t z?2>LO6WkV2!(n7q#y34={k%9Y8oBG|~vvrqr{?L=u7hB$M7 z66}3AE$=EhV||$?*W}?2om9>Bq{|w2uw+SEsQli7U~?g<{nuDJlyvhiQ~5wEKPyY! zyRO*9{=FchsxLZK&m;5q_$Yspx2(RrMbbbij75wocQPyLpWzS8?Jl~+_Pq&;w(j>? zvSw=zWDss{6t?Wri~jjqA(Awt!RfZqx6Uh4&mU`=K*wWq4ojne+3nh0G>|B&=*MvNh}@Ny#7n8`9i0bQ?*_B~{eKwZp*N_J2_~gQ znl`pVEsNIj_p5w5f4_t+n7N;x8;K}YLECoeALl*N^4DXMH$)+m};J&uo#ibn*3;I)Mc zZ{LzXzxpM<^Ty^mh2Y8*SJBMDY*ci5l~1qwVy*I6FhT5_*-)!lq_UdDfFT)O zN>L1yS)6pGB6l$18q+~VtCi%l>+LSLjBKOtY;aS~>{}9Y_w%=|6V2Tnc@LDt@q!Bh zU<=xt8rUrK?5;}6{{$B5zPvx;L7VCM3K^QcmJRgUs6+eDrdT6hzXQiRnE{Mew5IsfPQO`bZ`tFI&Q9Fm}R06nj?0Qe0 zT$X78Zl%zDxECG_5ObAz_VbRW+6iZ;tbOD9%b2abTJzL3?@Zx*!?TVA0vLPG+gvX1 z>hQ`od$(7<12B?j?4SHShFoKxG;t&;*U1>*V$o7ta7_AR2y+w2U^xTkHJ)BbvJ|-X z<0s6PF~^h$jETcqwO+a(#~GM~U80*2J6e;v&%&FqXsScI0yY%laAr`(a+kmso6U+F zP6kFp%9y^yzOw%qa2=)jVzj<0+rBGmuLV|>Q6tx=Di7(mvHWr1Ri^!GN|FuCvcHzB zpR)>0(evvOP&06=IVydVvn@qG2#`McbnWr4m76@3b0siib$}#2XD}(MbM@BVD5r<- zgC-7`c7~*q&IVT=Kg3PwbA&;6;9;Kwsw-7Y3Fs|Yx9cm8{8zGf4<*=G*a3L;m`bM6 z*#XeomZ}v{8@bY*bQQ1GTy(Hc_4ZP(FVeRR_TcM+t+Vcpzn=YpZ+&e74T!P&o;1Rn zuE`x}co1gtb`UP!ZPSh5wYio&xn)Rh(MeZrIu4TN+bLop#bsbG6HaW*xOCKIw2=!(&U+3)_XJDRGGc$F z4c1nT{v}Z;UXGYhE^D}D0lFVdo}ii%z^N5qWS@?}UpmQ4wJ0X1L^-ABBAFux>EG6g zm;+*u{Y}OvyHqKX>I=K#&K5t4LY~SCN38qyOR+z*9TPlmM@MU_3#->V?mzzzgNQ0t z_5QBn=cs8ujW4G*=tbiXgezA^usziXz4LrhOQ`fqWH{bKjmbUbaOD}tbGT);0Dzta zKQ$ye#2i4J>u8oUK#7Y915mX8hmlw>XPAYTiIC){b`3orxSM(U`MB_gg&T7>hEI(s z3!#*=w(DLxVcqU5=oqW2m80Z=<1TKLCc9~@8f6YgjZMm=cL4tql%;UBI;7CeT4QH( zF$(zoctc|%Wgr<%|ANcx9J}cRh}5k-l?!|s(&K@zu(qZVEfyc_KGLqBbp}6Mj1i{Y z-`Y+3DHb@~m75bb5RrMud>ra94WFRg6ph^no$@iNb||qsrTipLu_|)-N?#Hv(N2ogu6D>@ z>RWUdV9}&1`KPcWJ4jROQa5vA5{+&5pqk3L#Hb%PviI`jiy!HB1=haeSdf*{io@3Qh>Ke=+P-fk zkRJHjgMMk1 z6#9n+f1(jHdAx#KktDUgyf~;y@>Wd3(H|P_-=?S|{bJDlHdx!sI>WdsxRTkjzm2^! z@NEbxu%Vn+zrf3g20nd0viVTCGsAIAkf76Vc8k3q1Fs5>p%{e;O%>~akmP_b8VEy{ zPI4|~K$_6dEv&uObpFNuUeq{Q03mA)3{@qqRz#Kkv>EK6w5Es8BalZc5k_gBoEr8} zuJ%A&+t=Ox)}Y<$ZYTkMmRn)0S@Ho)z%oDlir=3gWpfzngbnKaKKaGI5$x~rc3k+YRw zD_0TlqEwcnK`;CcIpgJkRyTc@8&x?1&r`GH(s+g*Wts3}p}MSb%>&#+cZOOqb@npN z?IZ?3vqQ`n;_fomR;~kS9m=1`xksYbj2!=DyHP^DXWo1L(vtDJS#`Bvk}GoyFgDLP zc1&}^n5N`MTIdaNIWfGGRJy4Y!l~u(kF%zEJMJ&FSK;fb1f24QxfuG7WJ*}U%8XKY zIyK<7gd-*k36$WwOEo$D+OaX20(;qPSsSA!*)T1*CVg<>%lSZT<|;}DiM6kFVjU)P zM^hUecgRqCN;bQ+nzYMfYng7${TLFcy#CO0M=gh>p2C<~4Hq5%`&oq%i9dWsRCy4pWA^bkLTv*j9{ zq}90YCBGNV8KEy!-B-4^0lehPCxX0^X7iq_N-R=ikXB_QA&)8p#dY`e(_e@4${7^)1@wDP%ezv%cKikkDH*n}C6 z{^5wn)4bowsS3kWzauWn7hd)`(&q@6UwNUILu35&8M|T4HvWW($-END3w~Y#=sq?I ze4-cc!GX-G^YZb>`9xR!6&qe^hj}dLL}LxhAN)n2((&+`Zf5mjv0=5WdATJp%~9fG z);Du+IyBeGwSn!oQ)fbO2|}Jjn5Oju!v~f9lH`n!9?#Z>Mq2g9X!q!Y{D+g0jvo&I zcgQ@t@~scgcylMGSiyI9^8apus6^*A)oV4p9)<&08C_D+pHmn27qb#jB!x1A5(`H- z^5wXzT&`Qd_~(l+l*Q3XOyqJ@4n{4taj;cAk)l|KF7VDw)*YvvM-op z3pmfJyD#gNIhBwgwy*QP!v?PS)Y13)@4Ksuh29F*>3NC~nJJFAO|z7%!Md{1Ak5E( zfPp`jeSdYu%lj$S(MMD@+Bzwsm~aVj^@q1?l9gWQEx#Emy*Pf(!vQg-++Jlv5Fd}; z(Vg8^w?7y88QIkBiP4uV_a6qRYBOQQB!#EM1$b;{TV@>wpZhaUwF+1(sV*D$#o}N) z0!1xAt3~Un3*=`Aaw)l6L8`%}B9P}YsLci0B30Tej7F{^fU!)Qyq}3s$t!r(K0g1gdl8P&aH4NOG^J9SvA{&B$f$~o)%ztsAiN%)}Sc*gRek&9Fs z;AOdLqDUnSPDMKQ@Mi(eG-$TVMNjlSI^a<1?4I%LLcb?*8~E{RhkSg$`wbh*!{9Bo zWr1W3-xJq+V$fF+VVuA|@xZazEXWRARg~lSgW1=h$sGkyLiW}~uf-*YGB_B(!0Nu6 zTF(b=hXUFKd3PbcN#T&v-k*^QVe^%_Q~a*!G778%Y^_l$wSIkHjk82QyULy@45Wd6 z5S==<((1{m`nR~-1CKsS2pJDT5H1-5djaB~Gub|;N6Q?n#M71b zk~o7Z^d4Fkpa%|%aI9D>yzI_PQ)x|v6`jBD9_XCuJKHe-8d1XWQ9aD$udelv)L=>OCSUA+8RwMm+Y*rS4*4b^4Gi74spSA%>KsS+Sgo~_{ zY$`e-Q@j_6sI^BarMymn?`XyjOslbooQ~q5PboZU!gezxrpYris%w&hO1u`X?PKw0 z1LA|AYU^zC|SS{ zbjyCEsKsH*H^Jr@KFP{DH$h63n-5;k(d+DYtjQAj`D~2DfDPMW&0{~_iL!vN7O%lF zC1#q8$T6P6ey{t3LAzHm63T23*@@;YXL~<$Fage{uvoSA!WdyM4H&lFF3!9C5w{Cp zG3$FaLbm?y(b*!VP?$kt6^`6ezkVb34Fqz|*BF=3rS`k!jrWhUJm75(f1200R3|ef z!~73pjT$GGvvYU%O3dNgP7SuPJ>`h3j#<1_&jS`x#c42?!X%mA~Y>Zne;|&+)WtfNm>B+ubDgW$J@3G%L%k-W+Lu!~6 z`5#7Jb)qzT^!D}wC$6I1aR3+E!8)aZJVX#WMZFuuaExT;mU=+xqZ{7mBQ3+IfjsPE zzjE$leu;C(G*6;vQ2J`C;#JEQXGYGhuj$-R zb#3l4{Q|vYSy2bFCEiM&gGv|xVZkOrnp#vzd*!B2I?$x$&#Z_`Kcua)TIRa4JV*(9 z7>JoR7ZDd_P9&?qnU|E4+4&IT(lWx)_0K^vey59PA$pq;=2FyeWNLhecdM{C)D-w;53 zfO=Ph%s@>+O;@gQEv=#4%#Nij$)%qauNLNdCMbJRtjN*$(^xX>P?gN4f>M(r*{7lU zOAUs9dv7bgNsX!IQ5U*s(cqiB%-B6wKm+D9JUoIa1$=D>5xpbt=A_(?NCwFnpKyY5py|?+dU(r}_m~ zB(!zqCX!xW)rT%wyY%mLCxM-k?f0%}&DD#d+CJ6!b34ULT`3r3_zW{sRDjeg)1G@) z$nzdK&mcjFA1=grKJPyLnfni8tAQ4lt|6DeD%inzmfFTQ?mFmV-TPKgVD+7WqY6!n z15+u5g086h<{RoCwk~w8g2RUSSA*JoY7_rQ*I9(M!G+rzEAH0dlHim=aQBd4#k~}F zcPU!DL4reZcXuo9UaYuNC{nbA7XFhvxU+NS8Dy}ZWPkfx>wQ0qsR#v|-z>FVaEcH& zzR4?ZMLB9ufTi+hwYt16YzMKm3$o=IXB6LRc3}tPrsa zK3{wCs}}ktPut0CXb#Pcux$1x#|%`c`;4z_6HDAB&CM~FU-)wWp4sB&mgInsC%OD+ zqI$miO^qw7P7xZ~OW}U1k?IYET6X45wfyDxb9R{8-XFhxn|qN{4&)Sc9J|heHq3jD z*ZCOxQ2ow@IkvOz2i0c**N!t^`o{{Fi@~bUuFn=~^2H?Ep@p0%JRnhfKm{^TZ`n~+ zVt1PcAput~YaeuuebkE|47Njfb)~Ke=iO^&3mAl^!DGs`4oYRcCYxBpD5m)oelzuI z=BJ0;S`-@l7+hijc{x@_KYTI#fz@vGEg5a)33fb~*HC+0y>(lnU~glUi84X^i9YYF ztSBBRK0lJZEkA|Q2ya03ylFt!zn{@kF}QTeQxSK9hLso`ipufrY`2#}laFB*OY;qC z)(I+sL0v&rCF%9t9zD1S?q(`HhasfjL8|pj?05Xuoz2lez}CUB1Ef>!lec#MU6p%f z-i4{hW9ZGVXzI{wV^8r76Xi$B8;Jm~rW2scOMos96S-zt(y>w+mbk_QI3K-DsAgT< z{m@luY3vUy%X_%7=NfI)nEE^R73ru~*4+T6F- z8HanuRx495OvDSWmsy~Vs$@&YF``J2C&iE$V*Ml8sL=vhe8?hSoHWO`L1~WEdH>B7 zc%_>Mf}08r>eF{vSfJdDjXzY@Ugk|gNG7U;7tKh-{~sM!ELKg$@7>DkO3I?10={{)9Yl+7dbZx9TbKVIX;sn1-zEHC_X zawq@Sfvwp_NYeZKvx&decE)xuPgKP9nTCJ6$1RkwB{fk!IJg680@t>Q8>Yl#aO|o%Frj&|-zfbQTGZ(H#e_*rA#3IE`E<2MM%Hh_8vhY7{F-1| ztkiNtJ%D=uD=F;PE9EvUL?UMf6fIb3JNZwun z{@w=5jTq_%oz${?3=DUpN}{JD!f7&@XHsixoP~GgOX1f;=UM zn}(D6^oYx44Vq-Ha+~8evX}FW_BO#D?d`XR`W<<8D_iC|{7s+GiBj*Ej5nR*)Nulu zjMY3jJ+rh_aWK(DX18~9k?{ZF(*IDRXQvHKIb`u?Sm6g{VtR7HjB&^Ne#o{yybBGo z_ZdU7F`=l_R9(OFYUq={d*Az>_Q#M9Mq)(6o+jI3>uG;T50m#~P4mS5dS4lAVh-NR zL>A58+NOTP$07Hwe*M6=C0@g7%vDir2;??E^WcT_-u{Q8rrp@pb%0a#JEqd_Gap#( z3_y=lv(afR&Zm@tg(R|cIUa50HoY+99Yl^{tU}Y1$ZF#6Qu?H8JvZe>_qPFTP%|7x z`ltzO&H2Q-Wyf$6F1HGT=`zET)B+E~UP;Ku`wK1i>XPW)dZy?vReJF8{sZWm9hMuP zv6y@F?ATAfBe#IFC#|2qd3l^Hx-jL@0Z0s1rj=A?e*4{wXNF7q@tzrsej$u=?nzyk zwTtvW6#QUD6*1%2mQ2`s5`N;Y>z#E{`7}eO#yGdG({n6BPI=|0CZ6jHcR9>1CQSh! z9smBL{Yss`f8+v^Eoc$jm)Rul&o{Bcm4_`IZ3F3|%%7>S#P!i1 zT5Z=xo=OT|%VXgg``Sfh`uvd4bH1H%WQ^Tl35;uW9}&e?FB&U*XEc=mS)!}ia1!R^ zy*rYuepFfR>K?_?4yanq;Od}rrnpR`1St}XXjTg@hvHeAmrw2h2g`oH{&AkmttF$ITwrW$SjYyYd-c8*hI0` z4zVVSKRcY}G$(2uTBS|7NKogkgX2srk7*e0s|KB|I8cZjdePaj3Qc@(jev1Jh;oPF z;VXKb_*mD>ze^9kuZd+DVYrd^!jDy45N~}%XtLm-Z6DEpDC9+l@-vDMK1I@ZYt)f^^OSDFCIEfRN4`1#}jFs}UNs^tv_^zf%XvxhFflAZDfdNmn z=}$K4;>KSTVlNJ(z!X68pkNM zR*hj8WqUz}EW6G`*Y0V2Ygf9Or!tyy%+mAQzaBbD<}?@@t4;n(lb<#izweU-3-8bb zu2|>p41bt-v!v#WFEe(nkt=3^AFlHZt>`EnS)X~;t=@D0Za{bL)o_M zo8H>3nNqmW3K3^N>F%-86l#)W0#R-iJ?8CL$V3b_IaFhP3gB&?lfJ^2(7H5@*pi)X zf90<*eVb^6LsZ1ezm)r7v8$*Par?bVLX>pNchC4P;E{F?n}|CFxY1hKo|r+aDP`E? zS-HXRw&Y@U*;wdzKKXWsb?lKY-)B{xBB7m^99Nx=!fa!uw&s*ww5}`)ZR2s{YG@z| zWN5qS^M1{Nu|I{a*-!3;jfrpr;fhbo-aJC5iswIA({*Q>e_f3Q?ezR8NSdQcW?ofs z6%U!>m?x`)30e$p!(=jzZb=m+DrvnK@8}X&1&#NmXg|`jF0SvffT8K=l~;f|K(yAL&Lb?Xs1(CWL?&4#BlE@pLrvseIfYga9!P$E5>iiTa$ty7goF| z&R`QXB~AEAz*dLMg}+@k&w(0G%uDiNl8oxz23rWAVmF*!vu0sylq3wqVZ#ZiSc{Ln z)*W^ow(LxT(WsIkKv$_IXBgx=b!&5KPkK}GGpVUt=NZ?K@a_Llh8aC2|Bg&2VYR2) zNV1EeUsTxsywD=!H`zoKQ8MYR$ab7|J#%rcAFpP(+gbYWLuYgSu!%h0X+d^6uHVHO z-2FP`VI4-dv32HvUzIadmGZMCv|ZKjw+hY-%JU`o=WS7VknAr}42(biw1 z7H8`XbY$!Phk}P?_Vt59C2o)B}}&UatQ0dw66wp_H!J63_u z2>h!1`bl@NVU3{?8Sz$WCSL%{#o`Rlrf(oNA&Pp(k}crOGE{3O(Gj-~bvs8Nwlnqz z;)3kSJ)0u&3nCeVem^Z03#ovTWoT*&~Va)=`RL5CwS`ckcwhx%t_R;7G4up2qZkcrjwk3k?5Jgwgzl{ zGIxUwB$Yhl@;C|+QdFO(6FQ?!0_2i{k@YQ*ZzN$_TTJdMLl&veP9GBN5YTE*(=~#B z#nKPqsmgp)j8(QlpX;e*b84^!HUTP6zm~Ye+qt3Yn=T^d_-a}~>$S)z7iV9=%Cm9tG|A2(Qd3THkmv~Ct2zLLH)TDFX5zm`^ zC$EPxfkDUR4xuY9{#UpFlrUM556+DWlTE|_I!fBDseFi<5e?|BC_0Mt*s^K(jwHtp z$CepwNDMjKlg?|SY%+*VEG@|8#=5XA=>lM4b<%i`n;^)i0?sgSp%M^d9LGo~c;c4GBpID@()0G< znJcKjeQOp{`VJ^g^%~L{7gjE!Cs~Ef6CpGw+Wsffdj7ro!6uPQnq_j6$K)6&N|-*{ zDIlhjpCz^v50qRI_k$8Xm#3kTh#NtPRAG8`sQbjC^gOnZ$SmP*90B_b1HQ|$PFm&LPEfOR! zN73Uj53xkl8Q!w5ncdCBrPJ%UiYrOUSm>mW0^mefs-5_uHimk*K_b0zJbiHyj+A%uCA$Rpv@7F$*e5UG*C*~;(ts;D>7nJ?J>%oju z{Ur^4RgoV;&d4S!5Js|ah-jd9>Aq*%{vxoh<(@%M0g6%ji8V@iZ?OVU+iX%dyeLXk#YPzlS#Cu& zWjWaxr(_ZFz-rBBoqVyxOZwc1IyNQ*8LsLs7~ZO?u4xOUR$sID{tNEJ^h zvebTbwRCWCToBLF+>rqOlc)~aed@0E$GVVFcgErJZaBJYXTzP^B-4%I33B5m0{$d< zE5p7Fz%N^o50zJ`LF6-PKZD~ehM|1lN0JdmWOsFZEgSlbl23y!=aTX?alRcV= zr*|d&o$*JB>OEoP*$qMfQLG&SSxjt$V>}7t@gcYKfS?MToy=e3lmdQfP1hBVVV6Nj z+^wi4o53Iz5Pxl%5f|;xoVmtWEK8&nJ4j|dom9rd{nqtDJEkQ2N7oRJ-2qkxlZ$EI z_nlgl>dZxzh+Ip*)CxoCVapfI?GjTjO=f1X(MhW$=0uoe;J?wT_b_xyyIMmY%Ovj% zgC()k4RgH+6i_}vM*q}V($NGmhlI8PV^(rPGanV8idOg)^dk@Et*j77DVtytZ9^}W zriZN>$}&8X4JKE5(E`JydOxQ8~((PI>T8!rD?PR*?d5ry)g&*fQrbGHGAa@zRWfh@3CHfL%rdO2JWA?Z0}y| zu--QwHuIVT7BOrjHy8uIY0Vhbmhk&g4tURz zOhhvWQ0MTd{Q+2tD!0F_kg#SiQ8qb#c>d1(z7VxJqNFQSHPT+#g1|)9WpQ?KMxqD| zRDj0=CDnKTlnq!dgQbQsD!urxslgoRe`2++5I@ubV-@=DB zq$DfU@cT%(D&kj;1)1C#xA<3OFFA!`gBe@2cs)8+wv-lQ!;(EzfFei(UF1{ZdJ^D% z11BqkVEN<8Gds4zv?rfejIDaxnFCi&kGkRK7AHD*K3CvUv{7wGpk0|JPrZf+*Bxfk zI1j1Dk2$i&L4we04MY2ik2(#;*mwMcGZj2SJC*cH6PD=!7a&Yelthpt=E%@9UV5bz zQ{l5t2YieYKj1gY37~?)%D~;3X)$x!m|yhjYaFFazmk{|%fWP`QsoL$SIWY>Y%5hz z6im>Xiku>&5LhY5Z*pV9I9O#&Q!$H4xQsWrxE^Z*NfPC(WGFGCe{sNb5j(>~dt(Q{ z`J$$0Y0i@vL0gwpYhJIL0d90aR!SNHir=;D#8CCW<~SF#26&u~;2*1yE&(=}QZe zHolqmw%-#0Xz?R^tHQ0aMW*P9JcgIRV`VB7ueY0zQae}jv9@lyP3j~=`8v^!^xI40 z!$mNvxSrC`M1pj@KG_>F-OOE`%;CK~gw7O+WxCCiU&W@R5Jz#N-6SLKOyPQ-Mn7F@ z79uK2=Pp!wl7PM04?#Rmx-B#{9-{q#R)z@#@|ByO7FGOnLCVtm6U$xUZ9 z2uJQMZvvkkL=EAjR5E_+a$58??-IAmGv&F?DeA^YqaKAMgOUDt@+<5%&q=#_O5Km2 za>cLWzzekkSAB){v}J}iC|%eI-TpMMXnZ`w?13kh))sroOKde!?k-wfZ7 zTX}d~DmRWw=%ky!>Wa48=k`e9m=L+>RWx+5rPB}kTk~;YxS+_JRYqL5gI@) zc|p1^0ga1p2-oy83*8e<3=&k-IN=~IPTHpWTUV{|xE~X8 znHRFj#C14UpAG&E%~M{_>%8WjaG_r})FWW=hJG@8Zu#bN$ZIg;>i(n}Ky*{yZhvtkUsYf>7N^)s zHKPmaEBeA+pz@XnTxf-gm)jUWc2D8KTY3`d@_vrJoALW?Ld*~nFYd4rH(%-2wW5>! z-kB?mojza5X3k|pSCqs?Beu|3&j=cYAK`^{p-(TA4WJ{h4AK_KSQ=($!va(#atb)y zV1|`Xz_8SMyQ!;%#)8Bz<2L0OT`awFOWT)UfcV5R)X{5)K~(_qNqjdbvy5Ojls`h! z^HQi9JicfQoz*gY&pZAam7EkTJspr=tDE4R$U{8-Y2`YlrTgkz169rTiIeka4*_|? z7D@4EOs7j@i-Tt5WoxyuWwQCLkgqBi;6D^7Jyt*?s7y1I)(ae1BPygRq~C9KbT(~@&Uw-+b8X=2LZd(*7Ni} z$A6Y$l)^WVcYQTeqj!{%MUN{AI??y+but<+Bs52$4;Hb`QV_XGwN8CE{-?NScZqM* z&Wk-x5T9`Ph}lI0jhq~1%-T!L#&0OwMZNU=y4V(bY@YO4RIH_@zl_3t(!6=L#=T^H~Q9=X(Pk=E>pVCXUQq0F0p`I)^$zCA0V~)2pJ<$*;Vuq57o#wRLWF`ipz&?w1Yf^M2_P{~7h`-k!}K74`%39=udX8ygmPk`g@o+Fy4n(!o zGd6T^v0fH_vYSygd-0PtKeIhteeJKtCn+|a_UVS2@%41bEH3h}Rw_3}`ctLx$fe@b zP^ph?E^nObXHxRgE2W4!BNBwaC1n1dGn&a9-G?N+CoHKhKt;H3%u3zsIg?NS>`-;C z!pjRls~VAAl942U;YU)Xj#b$M>`~zM^b$JyuaEr~_a*tDTa>Q`FU~!+6*2Ug(8fT@ z+m$SqNKD(9nYyE3U$j?CV(ZRY;YC3;FabdR)HPm*zC+S?kLiACN{T0Z z+L#Thim1@CNjF%M&*cKl{y%~5N+6|W6@?j~Hgi6lXo7QB2)jZOt zV)^jh`MzUcJ#&V(#mpiYu?@&r)5Y`6k!Q0SvXFab@;RDE@a@nPw|!;7x%8>@V=v{8 zeT6*o&ndLdwa8ZbCddlkp8|~=I^i^|Fw$g@!y{o+*zT2Eo_595?v!T)EvF6@k#tbj z_Sq1~_%O(6PbR)^MTg5fK6d|-Mt2l@G>(U64OC{Ewvtq#whwlqJcI(89MY{H8;E2N zq}OW(Jn4?^*eBC}bw?Tu3;@eK0+(C(9*Qnrn~L%rzISRG3=v=gEcS=AITkj2HDnC! zAOqZEkOs)(6Mj{$#pm1v${S07T`0jG>e6i-D~+I4N8vY2t-6b~QqZ}ND2jIO!U5D3 zED7J~OsS4?WkJp*K2#5tC~!`m2T%0`cl;qfDK__8~Jd0@7s=$A`=clc&pqIKp? zDkm$4?ppEOTM2-n6|iHt)5&}~8;e61`gm*cN(SOA{iT<5Z1y8mdDoM|xNL*uzIv5_ zCNqh{&QfeZnosK9)aFma^f~R>5?Xx#sZK|GJVrei_Jo?xukuOHuU9(9t*e;U*70KTjo5Pzj{vcYIJPd;cNNJ@>5Ki{4G(a-Gf-)$Y=!p$CERM zcsjSN*x|H7CK6G2KMr2J$aVg0$aeG5ks=e9-NP(wsA9gLMcUR3EP}%v@8@a`;QUC2vglld7j!=Oot8YlZSZly z(I?gd%ttKf_uxE7m!lJScZ1ncuhm}a#W%pl?9Po9upqkvsQ0!4ueRPfU;C3shW+z z&Z75(`Gn{&atDJRbYID*9i{GS7e98sv z)*Jfa(!I_J68;1>TKolQvxL(-lOfw36@Ei=q#N6jajUSH=_+*8-Jpq`TF--Pj5dvT z1CT{7y|j@Q2U857%BFMLQuZ?Qd9%xjnGV3O*KZ1We{% zSAXd^Yzqa*ZZ^9pc_#i{&AT=gCRkIlo0e0}`E(}bx$!`u6z%SP+%Igf&`Rmy8S0pA zBHyL@+wF;xWNiANKG?Qmc!vShF!Ljoz~wxI_p3ESz)?pcMbZw$mqS!y=^+tTd9oaM!cc#$q_e`l36_w~O78D(SagUHLQgwc*Pjx$nx^Gr# z7E|$ekCONXKr0egHUaM<$EM3E^m>T2_88)L{On=h3;R?&;HK)I=TW2`mV5rmIy(?$gFR~_m zoQ0bg)%sS|=OI~O26$>BH|_)Sp1U$^G%ngUR%0$mP=OXk8;`;S_F^2D5g#=uitm0;AScsQUCm~%S2PeJ`w!(| zM#0?7{gbj)rQ-Rhrm_qc7IV@;n*$DV^BfULt|^0*fum?DlB53bEOV6o9GU<-s?yOr zpq#Fg&ce9lS;L$I0{yL~p6W!=`q%28Mm`82S`+Ndyn!MOn;VSjzS72h$;VKta~M|f zP;23K+|nXgD^T04lq{OeKI;3!PID^hL<4sXCI9DLbq?z`pGVEO7fG}SMC#LaS@N>~ zBKg3pmMDHlTP*ie0F65G(7k%gD{tXZ9b6 zoCzU82MHW>soF!dPRfU*0f}#3mIKQ?**!-}T0_tkHhZU9+O_KW1`G`?hr;gAfmcQn z)Bx8!702rGI3AZqGRSufBG@cn!q-;M99G5jpE%rzbn3jL)g`5Hi6E}PsAhTmQd1nK z!85zhx&Ct|NNE+nHkQlxL=r-1$C%PiMlj@x@Q(~ZTF%37600HZ#cu2zvWgQ)>{+4f z_vogZy{TGbK+}!#BRof^8v)ic=rf2|g3KhSCZ>Hs$&tEk-);{by1d4Bwccgh`N~DU zr48J5_HTxh>IojnCjR^My;}f-_=x30%1&MP@5?{_`dtTImpraKV zV@GU(eiis2^uTq{fq@|u3E#TpWzBux_E>6ho2`<&XbY5n)7`)oZn?!Xs(m6CUO9kn z)9jzg2y|z0mtnYHZ8R4-yh_i0jguBl7b2_~7A38ESJ%NQ2bmN9lO=k1{m02N=I=mXqFZ0n9oOm$a3w z9bV&FD40^+bd7zlUbo}8hu|7PW2tS`irERsLIk2k77727_3;^PM`t&-vjo4qY#V)l zRa&}~L%*#}U8CtBW`YT!qTv;HkY3OA&U(YbWy|o!g7eoHu?Uid#TOc!hM`Zy4@r5i z;>$Iz*dlabN^>uf%iGJhQZM1lB_KLK+(&ud0iD3Y*u4ssz0lQJiB_!xx0|v|usDWS z79E}Bt0E5R!q+1&BpG5$tf7I8&n@$Qx zQThc%?LckbFR{6&t92WS)HFfoO?AjTV*#-Ih7IFMtaTs|)R}b5DM$=5m zCMHU}1V!S$RDNyWdw-8z=~|_JR8a9?!c-hpF++|2OqTey&wjzESsssDj_cu-RmWUm z#%xg6bJYnytTym!SSMK)!(s9YC~7F$A+|9=kGa!PngBDkP9QEcggaBo{pdhSmQNS+ z(j8XxHSHZER6V{MznTqF=YIfH8i|4wh1zHFog));7N?;{cLcq1g-#vz9e#$pN>&};Sa;j7?{df2T%ixYN_*!@z61uFnAFN}Qkn^( zZZ3{j_`$;#zCbi{6L7`>fR_(H^*G9-a$oil0>s!nwHvwO+>NvKNsSC?5_cn}P)lKE z4^w0@VoHFGfEo3vH?dVCYRT}|V`n^QRWp>XpBuOX1e~LyU+CKi{d$klX^TjsxefSK z?bbnaSH#6u7nzadj&(FW-nJ+VCc{OP_yK#tM}>LJr`+j#AZ$`znEByJUC@)(97qfM zZkm#glel6)iy!fIN!ywey%I)ltK5cVx@^fTrGMN4d7V`z7dq))@+gQme1_~1U?*T8 z_X?ZRN4NT{eO%1eqIc3p7OHqD9e?g2)VLZEFEVRPGq28NF8U?6Y=SHO?v`)v@+w~ z<(a=`1ve|Y&j+CUcGV1XE9tSoyBLH=(eBOTv|JUBuv9=;uhh!1;dzZ&Fz8lw+W8wP zsfjI0<*AwR~Ay1cUu zViWFjLDYzyi=4o3wULR`PP>aR}@!xF0shoz3Y89^llO=W*kiS^Xx0!TstRT-PJAkAJ=;BD zE_>DsQx2zq0(&fiX!70NX||Jv%;Z(f5O^#cB1ZIOM2Z771%jGI8$Cn-LQyZgLkl&o z*n>G2K@P>3%t^;`4PGpKu5@*I02??Myu9N`ZqPzXV`Zs#+B({7)$2ka23a$~U(2T@ zhccDJg_bz9=0|bBDlMA zanU$Yo`@ia+O8v6=EqSUS|?%VsFKq>iRw9zj(8Iz0D>-pw3I`Vde*-f%kj`cX8ZS2 zX)m#oU%i_Jzh-~JK~JN4oGY;vn6OFutmCs(vDe5DN@RoOYh;&p8&hr!5Uj2nxhexp-t~yv71rRu&ul*1fX6%O_W%*v-j| ziEB{sl*aD9s)MVlk70cJO#z&(w`R&6o&aK;AX4%HKYy2nTP7H&Gi@yKRYD&|B&sM9 ztS}j|xRsTr`}mGS9VHqjNx?gWa*@$hDc_KgN^$!YJw&U$1r7v-R3Z+hnw53XYRw)B za_r>Td=C6(5y=XpV}+=3PckeTJOFNGl*(AB7bUR?+d#gj?N?eRL(D@iDMB$y$HY@9 z<&^{ELzSXT>#A^mkeP^_XSs;9Q6|WvU9hvMt^U{Mf%H392Q=LTQ|9@!&x)I2`56hZ zpE|3O#?9Lm?a9UY(7<3tqqdG^{D^61YYm<0$>0y@4skHs^evW7>^Y)|uz1T^JB&_q zZjue<=v?4WHE}!5HlkIG=QBq9I2H|su$Ukq8$IWe`wYRL!aT8o1CK8T-Gg*MwL7N( zLJ2!tQlUomlkJX&9R>+sd&WW-TaDwy0FBq*#R+>yS8MKmsgCbnX%jX#!ONhgF<{X@ z;bH6zJWyf@UHk7j<(hd2kn%TvO;`ems0;mH$1JpvnP%E#nlVl_z{&%w#wzjMN>Tbn zp7*Q75pi>1Ja}62pYDV}=|If1g+v5yzjc`?Knhvo$1o{0r@bCtq_aw!O%h9D$DC9l zqr#(6>C{PdkB^z%KV{4%Tg7^IXOP_HASj?BXJI1W>8E#41|1+)fIC3B?`EB~^>g#LC`TcuxCEF+bR zeN)|!PQhPDefRg-r;-xMlxF#Swul&+V#IvM{c-*^4~DGCn*6Vx=~ok4g0?=E8&2Ph zSi;h18IK6E2nADCGE4Jj4Ykv$IrQxZw7%T+V+nGGntJB|i+Ea6b0;K2$nmO5`~OrQ~R` zP7KZA6&#rh?5`#y+(Qz<{h-uT@agTj?ZT6@a2XUZc`B%_PH6ygm|1N>;Y>Pnv~U zO%+S&%2Hp42mC2*^EJ?5PX2PHw+2tVQe|doXwIfs?1G|K(wNm>$w&pZs)R(u7b|hq z=M-D;&;MQA>>u9fPb`C>dTR|)C(3Jdzm>E)th;{Ywh<{>suX9x33+|1jhd6B+iel* zWdenknYtT{l2giIOM>62YTKwF$a;qw&6~zQxjIJ&nDnQ64rSHRP9$f$B`B#qSyV`| zr1lU9;2+|T%c{v=$-zh|+r5235%)m#&a97#ojX^$aYL3~<26)X0ttrdvXYS|IbW*p z9b<~qZTgoh!q;W!`8H;BJAD4cm~512iA*4-Se`!8On~3iO z9fted|DmLSKTRoVsJI@~aR58)EYk|Tg$?k2aFt@p>`aWsW$nQY-o>g@ko+d=wA{l_~?_JLjI^aWbVTwnT}rfK>iN>G-saRK;A#tDe$zQ935a zDUW_JdlbkBE9R@lGF;xg2nU2>{&_;iXrMVVBp+^!xc4?03?$vemZ)7)h)1SE#gcwC zPM*3h@6IO!8~@mlcGaPvV=$m^7Z~S572+`yr%A3-kP!gq)b?V{mG3*U*R6^vlTRrF zQ*E7Ja_hy=VN?uUmS(WE=bmN1EYlzBz>SXz4p#h?OlYC7IZL?Zm8w4sQEd2HOtqAS zK6$L+uSFL3!;h85IhVaqk?shd2zxLKl@@XZ5C~6ydo5_Z_M&FI_ z3_vAicPgsNowhYJ5`&sXM$TQ*_aRZxPl*%pqp2y)FLz1rD*KJSbP=DK;Cr74J01j$ zL@~gzz*xQSU$UsXTp8)5QYYdjo0MHJGBBc{2N)hbpK`05%9+<%l4aj=&`Vh%Fg=Fj z+d-caUX~o;%@eh$T`0*aPpFJbz;E2nH}Xta;x2*tqGg**e0A|mRvx%~<0vjfs}6>= zRa241ip4!x8U*R7_^=#H^b1Yv={1@9dUr}hnv^2p4heCd{dgV>ZLquvwNL6e=2*o8 z46c&q>mDTv32B9qFEA#H_rcG*?NL4s_)4F?Q}L@U&pz8qB8zw58W*H^(P#y6?tCGA zMGh>Hb5hzc1-b%y60p z`Ys8Etuh#eF9(1PPEJ5`_z`Lnp@l#z#>;n&FZU>&QDkpf(eYn}8S)wq=RQXob|JD# zn3C6WF?ev+5C=ciSb1QaGh*TLymbGr;*u%+Tz5LkeO<{+wv?M-w6Ue(;4^%$c!jhL zsu|t%r7&C%e_I&GlqeW|jj%N+3mA1&B%N(Zy-8{bdO@^#1n2$~vSU^8H4HBV%(2`P zZ$&RQ+nNe(XKa!5eCD6#$u>BMMQiaN#kopcFYnMx5}TOfAQvZ=dF9?_e@ab7t~`Xx z+caJZd1peni@cmkznV$D-7Cz@jOGK0(D<^k0V;}hmj48G5&dZ=xtW<#O-u4 zm(@#r&U`j$b@(ZdOm0dc4P!+z5oUSiSUV&3#+dEl&|*VSgqf)I6eZoL@6QY4;_;Lw z(L(%(Ki~nhqhe)9NrL(3B41HR>52sn^E`{Rn;OHQt=yO^j$Tbno%2^Tk#;A|9fgq zUr-e$KAP3rp(J5=qQW(@qA(16!&Ek<&si*ho0@??A>6H-eBH-=|ee+JOkvQxjv$qm2nw8Rx>D9?@r-D5Ani=nRo z0g#Q|MG*z)F!x3I5jU^Y5?Y=@mWUl}LOkoxTo>TwuFMa z@JIBzKhh3fYxWD_VYmt<4L@L1WA`IkX+|PCcDRbA7rF-0y@msFxU!6sY_KuS(v<~`~t*|S^evzG}O6ai-q7GZ5nEDfaW(9Z5 z_R*!G|MjFLW7+^Z#yQTD%@2O`toyD1zXVZZ)e)vF$(;|=lbd(U=E9uF`lzf=lX2={{Hki%w@9LUstNgg)(MXQ>O zOR5@kxaK&2Bo30T3c6s=%<&PX>R>V-#)JM~J-oCkc&U|> z|1HBV=}r*zs>an0IP^jrclr0_TaImx$@7u=y70l7q;A3pf12mB2zQW&2*%kgZj#$8 zdFwrz<@7h8`RS%jZ_FPyf+rftbUrRw)5S@GPil%-wxty z;XVhrC29CHpHPX;$0aSOX|&)hc?OXZhZOp02# zzNaQI9}T;mhg#ml56ZV|8>4BvKZk2Zoms9ihytHEYdm7Y#yQC;-8zO206*^q-QIf` z@qxsst;eIl0Fvi;;@oyywLbwTLPE)#Gw${RC^uQC$k1tpH#)szz(#Zp8P28jG5+WJ z^#4#8C$5h>stMncljWN955~cTr-&hBtH#?nxU->d)QU5s7pEzGw4FN+n|=j%zm!#)xATTN*cd0v4hUgM#ny zB>9x%NW#lShr(%5+r-Q+m6TJ!I12~yyPdhY-w0?`!6ySVa&$IJP0d1>^bjodyj{Jd z_Sc%l7TTV~v=iflX_Bu449y$^t(gy@lU|{{OZNr*>#s@Fys46AD$FcdLeIXog4&>0 z1yMp~m>5KQ#T#^vu#2Q8%F|@Q|4?27*(As5|3leM-(RPqwh0v=rITn&VPbf4{;NSO zx0yMR`f9nQnp?z2z2C~OG|PgoQsFf*dZ=7L(T7`$7QY-%W*iEgqEY;q+DZr}&g@uw zTqUYbmz7&XgWAVXs3G~^{m|++#c3`sO$XE#-u;YsjM?=V?2EkMFlMsMwF3YWe{Mm} z$Xzcu)WFkhOdBz#I546Mm14#0h})aq5}c#w4ZLFwt^KvW&sPCC%2f!1FS=t}m{(`t zD9C)H_LjfWJB;!#$Dxgz{+VF5Ms_E@Y{{M6JWXYhCtp)V9oY32)x24)m?7o`BvgCKG+LU1j>QnrxB6k?89(UiFqn(48l5Wd#dYVI^@3l zShV&ihi{(DLwmcf%2VSE8E>MPn}S}a=0U%EHOJq(%4no-s$l{Vhsg@3In?mca-&?@ zvP4wSW{&qj<%ymweRC~2F@BKD3<&XdfO&8dvv%Qk$f=y_=NLqBus-;W-`LHe7sJw$ zhhc0PAEmc5Rf_nbFZztV31>-vOV2ze1-Th@rEgVWS~?a}%6S}|7v zIj)Ez-^O%}#F&7rR@R-74Q)S*Q>}Ez1RD{neb`hFHC!bT^95c2e`U-oPMmcD`S(AOT&aN zpE`Tc@ceg~O7k(fXc7n{^;lhe_ZStpazFeA`g6?^six^@n=fUtXG@#o z9swypYd(^+-`7w}sv460{tw0Ue;7OKx2E5KZG#{oozk_@oiaKHqZu_)x;rHW1f(`# zG}17-yHV-xE+qv8q{ZO-?D_qD-{busc6@f<*L_{*3FeN+Nx9h+_kAnm-u+W}v85~J z(9+f=Ik`@&0sh3Huqb?Azcf^oCJc{b^AKq9g#E$@dZ^pwcFnU(>wd#uS9;hry3Sd7 z*AUE4;zYP6cOw3lOoEu^M~_alUoul>>+?tE=e{F4v;X!MUVX+D*&dI3~Jnb+UKy|8dI{WzR_+>Sr!RA83Lb0m8hl>|B< zOzb~1oZe^+%rCheGg)~D7`YT^X2P#?zFQETg}VzksHC+!`H>8(@;;+v)e}Tl;@Iu~zG%ngGwy++J#lNLx?^eTLr{o6?CC5zzKJsSUrWYu(8Oz)kcg<2%M| zRZTD(aVkYLlnJx8ml;HkV6;@a!vD)hY4=BZyCjauq*bX|x?@FE{(D;S&u<4gae3d- zm8A|O#4Mih;Oexe06nG+Y8Q3xR;~mX8H6?FG8_Tm$ML#1d;_dThOW!r!mvrH!V1EL zqn7-vSZWcN@T?B6+2UT_RfMDB-l~+5|GQjYHjm7Z^mcz4?yrbd|2*ddZ|~eawhFT4 zIXM%W)7pRI<%N3+A^nq%4T< zC}rN5Z>eTFAw_FI*fB0_9oqbJB5$s>4PV~ezjD8rS+er8r}mvVCF=V?Ai72iv`17(}AR89`^QyLIJIi z%XQ-QdI3T6R`|gjt#6fY=Y^ZjZ*)4aZ$-fO;F`cxj}f;tsJPmlRLDGO>HIO >sP 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�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

    K)NeQ*z)|$pk*+bz$ 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 Date: Sat, 16 Nov 2024 18:44:04 +0100 Subject: [PATCH 36/65] Feature/improve language localization for de 20241116 (#4051) * Update translations * Update changelog --- CHANGELOG.md | 1 + apps/client/src/locales/messages.ca.xlf | 322 +++++++++++----------- apps/client/src/locales/messages.de.xlf | 326 +++++++++++----------- apps/client/src/locales/messages.es.xlf | 322 +++++++++++----------- apps/client/src/locales/messages.fr.xlf | 322 +++++++++++----------- apps/client/src/locales/messages.it.xlf | 342 +++++++++++++----------- apps/client/src/locales/messages.nl.xlf | 322 +++++++++++----------- apps/client/src/locales/messages.pl.xlf | 322 +++++++++++----------- apps/client/src/locales/messages.pt.xlf | 322 +++++++++++----------- apps/client/src/locales/messages.tr.xlf | 322 +++++++++++----------- apps/client/src/locales/messages.xlf | 316 +++++++++++----------- apps/client/src/locales/messages.zh.xlf | 322 +++++++++++----------- 12 files changed, 1869 insertions(+), 1692 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c136e5f5..b57b6f9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Extended the assistant by a holding selector - Separated the _FIRE_ / _X-ray_ page - Improved the usability to customize the rule thresholds in the _X-ray_ page by introducing range sliders (experimental) +- Improved the language localization for German (`de`) - Improved the language localization for Italian (`it`) - Upgraded `ngx-skeleton-loader` from version `7.0.0` to `9.0.0` - Upgraded `prisma` from version `5.21.1` to `5.22.0` diff --git a/apps/client/src/locales/messages.ca.xlf b/apps/client/src/locales/messages.ca.xlf index d4260f52..feff4f76 100644 --- a/apps/client/src/locales/messages.ca.xlf +++ b/apps/client/src/locales/messages.ca.xlf @@ -26,7 +26,7 @@ apps/client/src/app/components/header/header.component.ts - 229 + 230 @@ -58,7 +58,7 @@ Finances Personals apps/client/src/app/app.component.html - 56 + 57 @@ -66,11 +66,11 @@ Mercats apps/client/src/app/app.component.html - 60 + 61 apps/client/src/app/components/header/header.component.html - 383 + 398 apps/client/src/app/components/home-market/home-market.html @@ -86,7 +86,7 @@ Recursos apps/client/src/app/app.component.html - 63 + 64 apps/client/src/app/components/header/header.component.html @@ -94,7 +94,7 @@ apps/client/src/app/components/header/header.component.html - 286 + 291 apps/client/src/app/pages/resources/overview/resources-overview.component.html @@ -106,15 +106,15 @@ Sobre apps/client/src/app/app.component.html - 69 + 70 apps/client/src/app/components/header/header.component.html - 112 + 117 apps/client/src/app/components/header/header.component.html - 354 + 364 @@ -122,7 +122,7 @@ Blog apps/client/src/app/app.component.html - 72 + 73 apps/client/src/app/pages/blog/2021/07/hallo-ghostfolio/hallo-ghostfolio-page.html @@ -204,6 +204,10 @@ apps/client/src/app/pages/blog/2024/09/hacktoberfest-2024/hacktoberfest-2024-page.html 187 + + apps/client/src/app/pages/blog/2024/11/black-weeks-2024/black-weeks-2024-page.html + 167 + apps/client/src/app/pages/blog/blog-page.html 5 @@ -214,7 +218,7 @@ Registre de canvis apps/client/src/app/app.component.html - 76 + 77 apps/client/src/app/pages/about/changelog/changelog-page.html @@ -226,11 +230,11 @@ Característiques apps/client/src/app/app.component.html - 78 + 79 apps/client/src/app/components/header/header.component.html - 341 + 351 apps/client/src/app/pages/features/features-page.html @@ -242,7 +246,7 @@ Preguntes Freqüents (FAQ) apps/client/src/app/app.component.html - 82 + 83 apps/client/src/app/pages/about/overview/about-overview-page.html @@ -254,7 +258,7 @@ Llicències apps/client/src/app/app.component.html - 87 + 88 apps/client/src/app/pages/about/license/license-page.html @@ -266,19 +270,19 @@ Preu apps/client/src/app/app.component.html - 96 + 97 apps/client/src/app/components/header/header.component.html - 98 + 99 apps/client/src/app/components/header/header.component.html - 297 + 303 apps/client/src/app/components/header/header.component.html - 368 + 379 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -290,7 +294,7 @@ Política de privacitat apps/client/src/app/app.component.html - 102 + 103 apps/client/src/app/pages/about/privacy-policy/privacy-policy-page.html @@ -302,7 +306,7 @@ Comunitat apps/client/src/app/app.component.html - 120 + 121 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -350,7 +354,7 @@ El risc d’assumir pèrdues en les inversions és substancial. No és recomanable invertir diners que pugui necessitar a curt termini. apps/client/src/app/app.component.html - 199 + 200 @@ -359,27 +363,27 @@ snake-case apps/client/src/app/app.component.ts - 63 + 64 apps/client/src/app/app.component.ts - 65 + 66 apps/client/src/app/app.component.ts - 69 + 70 apps/client/src/app/app.component.ts - 73 + 74 apps/client/src/app/components/header/header.component.ts - 80 + 81 apps/client/src/app/components/header/header.component.ts - 85 + 86 apps/client/src/app/core/paths.ts @@ -448,7 +452,7 @@ snake-case apps/client/src/app/app.component.ts - 70 + 71 apps/client/src/app/core/paths.ts @@ -465,7 +469,7 @@ snake-case apps/client/src/app/app.component.ts - 74 + 75 apps/client/src/app/core/paths.ts @@ -482,7 +486,7 @@ snake-case apps/client/src/app/app.component.ts - 76 + 77 apps/client/src/app/core/paths.ts @@ -515,15 +519,15 @@ snake-case apps/client/src/app/app.component.ts - 77 + 78 apps/client/src/app/components/header/header.component.ts - 81 + 82 apps/client/src/app/components/header/header.component.ts - 86 + 87 apps/client/src/app/core/paths.ts @@ -561,13 +565,17 @@ apps/client/src/app/pages/blog/2023/11/hacktoberfest-2023-debriefing/hacktoberfest-2023-debriefing-page.component.ts 14 + + apps/client/src/app/pages/blog/2024/11/black-weeks-2024/black-weeks-2024-page.component.ts + 15 + apps/client/src/app/pages/faq/overview/faq-overview-page.component.ts 14 apps/client/src/app/pages/pricing/pricing-page.component.ts - 39 + 41 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -580,15 +588,15 @@ snake-case apps/client/src/app/app.component.ts - 78 + 79 apps/client/src/app/components/header/header.component.ts - 82 + 83 apps/client/src/app/components/header/header.component.ts - 87 + 88 apps/client/src/app/core/paths.ts @@ -621,7 +629,7 @@ snake-case apps/client/src/app/app.component.ts - 79 + 80 apps/client/src/app/components/admin-settings/admin-settings.component.ts @@ -629,11 +637,11 @@ apps/client/src/app/components/header/header.component.ts - 83 + 84 apps/client/src/app/components/header/header.component.ts - 88 + 89 apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.component.ts @@ -641,7 +649,7 @@ apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 38 + 40 apps/client/src/app/core/http-response.interceptor.ts @@ -679,6 +687,10 @@ apps/client/src/app/pages/blog/2023/11/black-week-2023/black-week-2023-page.component.ts 16 + + apps/client/src/app/pages/blog/2024/11/black-weeks-2024/black-weeks-2024-page.component.ts + 16 + apps/client/src/app/pages/faq/saas/saas-page.component.ts 15 @@ -694,11 +706,11 @@ snake-case apps/client/src/app/app.component.ts - 80 + 81 apps/client/src/app/components/header/header.component.ts - 89 + 90 apps/client/src/app/core/auth.guard.ts @@ -722,7 +734,7 @@ apps/client/src/app/pages/pricing/pricing-page.component.ts - 40 + 42 @@ -731,15 +743,15 @@ snake-case apps/client/src/app/app.component.ts - 81 + 82 apps/client/src/app/components/header/header.component.ts - 84 + 85 apps/client/src/app/components/header/header.component.ts - 90 + 91 apps/client/src/app/core/paths.ts @@ -1495,7 +1507,7 @@ apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 83 + 135 apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html @@ -2135,7 +2147,7 @@ apps/client/src/app/components/header/header.component.html - 258 + 263 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -2203,7 +2215,7 @@ libs/ui/src/lib/assistant/assistant.html - 127 + 155 @@ -2331,7 +2343,7 @@ apps/client/src/app/pages/portfolio/portfolio-page-routing.module.ts - 41 + 46 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -2363,7 +2375,7 @@ apps/client/src/app/components/header/header.component.html - 240 + 245 @@ -2375,7 +2387,7 @@ apps/client/src/app/components/header/header.component.html - 250 + 255 @@ -2387,7 +2399,7 @@ apps/client/src/app/components/header/header.component.html - 274 + 279 @@ -2395,7 +2407,7 @@ Millora la teva Subscripció apps/client/src/app/components/header/header.component.html - 180 + 185 apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html @@ -2407,7 +2419,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 278 + 288 @@ -2415,7 +2427,7 @@ Renova la teva Subscripció apps/client/src/app/components/header/header.component.html - 186 + 191 apps/client/src/app/components/user-account-membership/user-account-membership.html @@ -2423,7 +2435,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 284 + 294 @@ -2431,7 +2443,7 @@ Tu apps/client/src/app/components/header/header.component.html - 206 + 211 @@ -2443,7 +2455,7 @@ apps/client/src/app/components/header/header.component.html - 224 + 229 @@ -2451,7 +2463,7 @@ El meu Ghostfolio apps/client/src/app/components/header/header.component.html - 265 + 270 @@ -2459,7 +2471,7 @@ Sobre Ghostfolio apps/client/src/app/components/header/header.component.html - 306 + 316 apps/client/src/app/pages/about/overview/about-overview-page.html @@ -2471,7 +2483,7 @@ Iniciar Sessió apps/client/src/app/components/header/header.component.html - 397 + 412 apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html @@ -2483,7 +2495,7 @@ Primers Passos apps/client/src/app/components/header/header.component.html - 407 + 422 @@ -2491,7 +2503,7 @@ Oooh! El testimoni de seguretat és incorrecte. apps/client/src/app/components/header/header.component.ts - 244 + 245 apps/client/src/app/components/user-account-settings/user-account-settings.component.ts @@ -2602,8 +2614,8 @@ 84 - apps/client/src/app/pages/portfolio/fire/fire-page.html - 198 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 88 @@ -2643,7 +2655,7 @@ Taula apps/client/src/app/components/home-holdings/home-holdings.html - 17 + 16 @@ -2651,7 +2663,7 @@ Gràfic apps/client/src/app/components/home-holdings/home-holdings.html - 20 + 19 @@ -2659,7 +2671,7 @@ Gestionar Activitats apps/client/src/app/components/home-holdings/home-holdings.html - 65 + 63 @@ -2970,8 +2982,8 @@ 89 - apps/client/src/app/pages/portfolio/fire/fire-page.html - 122 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 12 @@ -3079,7 +3091,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 203 + 213 @@ -3099,7 +3111,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 207 + 217 @@ -3115,7 +3127,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 211 + 221 @@ -3131,7 +3143,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 215 + 225 @@ -3143,7 +3155,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 230 + 240 @@ -3159,7 +3171,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 242 + 252 @@ -3187,7 +3199,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 210 + 223 @@ -3199,7 +3211,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 220 + 233 @@ -3211,7 +3223,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 224 + 237 @@ -3223,7 +3235,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 246 + 259 @@ -3235,7 +3247,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 249 + 262 @@ -3295,7 +3307,7 @@ Please enter your coupon code: apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 115 + 124 @@ -3303,7 +3315,7 @@ Could not redeem coupon code apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 125 + 134 @@ -3311,7 +3323,7 @@ Coupon code has been redeemed apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 137 + 146 @@ -3319,7 +3331,7 @@ Reload apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 138 + 147 @@ -3331,7 +3343,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 264 + 274 @@ -3339,7 +3351,7 @@ Try Premium apps/client/src/app/components/user-account-membership/user-account-membership.html - 41 + 51 @@ -3347,7 +3359,7 @@ Redeem Coupon apps/client/src/app/components/user-account-membership/user-account-membership.html - 55 + 65 @@ -4187,7 +4199,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 306 + 324 @@ -4773,6 +4785,10 @@ apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html 32 + + libs/ui/src/lib/assistant/assistant.html + 127 + Load Dividends @@ -5186,14 +5202,6 @@ 351 - - FIRE - FIRE - - apps/client/src/app/pages/portfolio/fire/fire-page-routing.module.ts - 13 - - FIRE FIRE @@ -5226,28 +5234,20 @@ 67 - - Ghostfolio X-ray uses static analysis to identify potential issues and risks in your portfolio. - Ghostfolio X-ray uses static analysis to identify potential issues and risks in your portfolio. - - apps/client/src/app/pages/portfolio/fire/fire-page.html - 111 - - Currency Cluster Risks Currency Cluster Risks - apps/client/src/app/pages/portfolio/fire/fire-page.html - 141 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 31 Account Cluster Risks Account Cluster Risks - apps/client/src/app/pages/portfolio/fire/fire-page.html - 160 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 50 @@ -5299,11 +5299,11 @@ apps/client/src/app/pages/pricing/pricing-page.html - 129 + 134 apps/client/src/app/pages/pricing/pricing-page.html - 191 + 201 @@ -5315,11 +5315,11 @@ apps/client/src/app/pages/pricing/pricing-page.html - 133 + 138 apps/client/src/app/pages/pricing/pricing-page.html - 195 + 205 @@ -5331,11 +5331,11 @@ apps/client/src/app/pages/pricing/pricing-page.html - 137 + 142 apps/client/src/app/pages/pricing/pricing-page.html - 199 + 209 @@ -5347,11 +5347,11 @@ apps/client/src/app/pages/pricing/pricing-page.html - 141 + 146 apps/client/src/app/pages/pricing/pricing-page.html - 219 + 229 @@ -5379,7 +5379,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 153 + 158 @@ -5387,7 +5387,7 @@ For new investors who are just getting started with trading. apps/client/src/app/pages/pricing/pricing-page.html - 123 + 128 @@ -5395,11 +5395,11 @@ Fully managed Ghostfolio cloud offering. apps/client/src/app/pages/pricing/pricing-page.html - 152 + 157 apps/client/src/app/pages/pricing/pricing-page.html - 251 + 261 @@ -5407,7 +5407,7 @@ For ambitious investors who need the full picture of their financial assets. apps/client/src/app/pages/pricing/pricing-page.html - 184 + 194 @@ -5415,7 +5415,7 @@ Email and Chat Support apps/client/src/app/pages/pricing/pricing-page.html - 247 + 257 @@ -5423,7 +5423,7 @@ One-time payment, no auto-renewal. apps/client/src/app/pages/pricing/pricing-page.html - 288 + 298 @@ -5431,7 +5431,7 @@ It’s free. apps/client/src/app/pages/pricing/pricing-page.html - 309 + 327 @@ -6035,7 +6035,7 @@ Find holding... libs/ui/src/lib/assistant/assistant.component.ts - 139 + 144 @@ -6043,7 +6043,7 @@ Week to date libs/ui/src/lib/assistant/assistant.component.ts - 212 + 225 @@ -6051,7 +6051,7 @@ WTD libs/ui/src/lib/assistant/assistant.component.ts - 212 + 225 @@ -6059,7 +6059,7 @@ Month to date libs/ui/src/lib/assistant/assistant.component.ts - 216 + 229 @@ -6067,7 +6067,7 @@ MTD libs/ui/src/lib/assistant/assistant.component.ts - 216 + 229 @@ -6075,7 +6075,7 @@ Year to date libs/ui/src/lib/assistant/assistant.component.ts - 220 + 233 @@ -6083,7 +6083,7 @@ year libs/ui/src/lib/assistant/assistant.component.ts - 224 + 237 @@ -6091,7 +6091,7 @@ years libs/ui/src/lib/assistant/assistant.component.ts - 246 + 259 @@ -6127,7 +6127,7 @@ Asset Classes libs/ui/src/lib/assistant/assistant.html - 138 + 166 @@ -6135,7 +6135,7 @@ Reset Filters libs/ui/src/lib/assistant/assistant.html - 157 + 185 @@ -6143,7 +6143,7 @@ Apply Filters libs/ui/src/lib/assistant/assistant.html - 167 + 195 @@ -7030,8 +7030,8 @@ Inactive Inactive - apps/client/src/app/pages/portfolio/fire/fire-page.html - 217 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 107 @@ -7095,7 +7095,7 @@ Threshold Min apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 9 + 54 @@ -7103,7 +7103,7 @@ Threshold Max apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 44 + 92 @@ -7111,7 +7111,7 @@ Close apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 77 + 129 @@ -7127,7 +7127,7 @@ No auto-renewal. apps/client/src/app/components/user-account-membership/user-account-membership.html - 62 + 72 @@ -7378,14 +7378,6 @@ 93 - - Allocation Cluster Risks - Allocation Cluster Risks - - apps/client/src/app/pages/portfolio/fire/fire-page.html - 179 - - Glossary Glossary @@ -7436,6 +7428,30 @@ 21 + + Threshold range + Threshold range + + apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html + 9 + + + + Ghostfolio X-ray uses static analysis to uncover potential issues and risks in your portfolio. Adjust the rules below and set custom thresholds to align with your personal investment strategy. + Ghostfolio X-ray uses static analysis to uncover potential issues and risks in your portfolio. Adjust the rules below and set custom thresholds to align with your personal investment strategy. + + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 5 + + + + Economic Market Cluster Risks + Economic Market Cluster Risks + + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 69 + + diff --git a/apps/client/src/locales/messages.de.xlf b/apps/client/src/locales/messages.de.xlf index 903e82ce..796fe7b1 100644 --- a/apps/client/src/locales/messages.de.xlf +++ b/apps/client/src/locales/messages.de.xlf @@ -22,7 +22,7 @@ Das Ausfallrisiko beim Börsenhandel kann erheblich sein. Es ist nicht ratsam, Geld zu investieren, welches du kurzfristig benötigst. apps/client/src/app/app.component.html - 199 + 200 @@ -554,7 +554,7 @@ apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 83 + 135 apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html @@ -790,7 +790,7 @@ apps/client/src/app/components/header/header.component.html - 224 + 229 @@ -834,7 +834,7 @@ apps/client/src/app/components/header/header.component.html - 240 + 245 @@ -846,7 +846,7 @@ apps/client/src/app/components/header/header.component.html - 250 + 255 @@ -866,7 +866,7 @@ apps/client/src/app/components/header/header.component.html - 258 + 263 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -890,7 +890,7 @@ apps/client/src/app/components/header/header.component.html - 274 + 279 @@ -898,7 +898,7 @@ Ressourcen apps/client/src/app/app.component.html - 63 + 64 apps/client/src/app/components/header/header.component.html @@ -906,7 +906,7 @@ apps/client/src/app/components/header/header.component.html - 286 + 291 apps/client/src/app/pages/resources/overview/resources-overview.component.html @@ -918,19 +918,19 @@ Preise apps/client/src/app/app.component.html - 96 + 97 apps/client/src/app/components/header/header.component.html - 98 + 99 apps/client/src/app/components/header/header.component.html - 297 + 303 apps/client/src/app/components/header/header.component.html - 368 + 379 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -942,15 +942,15 @@ Über apps/client/src/app/app.component.html - 69 + 70 apps/client/src/app/components/header/header.component.html - 112 + 117 apps/client/src/app/components/header/header.component.html - 354 + 364 @@ -958,7 +958,7 @@ Ich apps/client/src/app/components/header/header.component.html - 206 + 211 @@ -966,7 +966,7 @@ Mein Ghostfolio apps/client/src/app/components/header/header.component.html - 265 + 270 @@ -974,7 +974,7 @@ Über Ghostfolio apps/client/src/app/components/header/header.component.html - 306 + 316 apps/client/src/app/pages/about/overview/about-overview-page.html @@ -986,11 +986,11 @@ Features apps/client/src/app/app.component.html - 78 + 79 apps/client/src/app/components/header/header.component.html - 341 + 351 apps/client/src/app/pages/features/features-page.html @@ -1002,11 +1002,11 @@ Märkte apps/client/src/app/app.component.html - 60 + 61 apps/client/src/app/components/header/header.component.html - 383 + 398 apps/client/src/app/components/home-market/home-market.html @@ -1038,7 +1038,7 @@ apps/client/src/app/components/header/header.component.ts - 229 + 230 @@ -1046,7 +1046,7 @@ Ups! Falsches Sicherheits-Token. apps/client/src/app/components/header/header.component.ts - 244 + 245 apps/client/src/app/components/user-account-settings/user-account-settings.component.ts @@ -1058,7 +1058,7 @@ Aktivitäten verwalten apps/client/src/app/components/home-holdings/home-holdings.html - 65 + 63 @@ -1150,7 +1150,7 @@ Einloggen apps/client/src/app/components/header/header.component.html - 397 + 412 apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html @@ -1245,8 +1245,8 @@ 89 - apps/client/src/app/pages/portfolio/fire/fire-page.html - 122 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 12 @@ -1362,7 +1362,7 @@ libs/ui/src/lib/assistant/assistant.html - 127 + 155 @@ -1414,7 +1414,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 210 + 223 @@ -1426,7 +1426,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 220 + 233 @@ -1438,7 +1438,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 224 + 237 @@ -1450,7 +1450,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 246 + 259 @@ -1462,7 +1462,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 249 + 262 @@ -1498,7 +1498,7 @@ Datenschutzbestimmungen apps/client/src/app/app.component.html - 102 + 103 apps/client/src/app/pages/about/privacy-policy/privacy-policy-page.html @@ -1510,7 +1510,7 @@ Blog apps/client/src/app/app.component.html - 72 + 73 apps/client/src/app/pages/blog/2021/07/hallo-ghostfolio/hallo-ghostfolio-page.html @@ -1592,6 +1592,10 @@ apps/client/src/app/pages/blog/2024/09/hacktoberfest-2024/hacktoberfest-2024-page.html 187 + + apps/client/src/app/pages/blog/2024/11/black-weeks-2024/black-weeks-2024-page.html + 167 + apps/client/src/app/pages/blog/blog-page.html 5 @@ -1602,7 +1606,7 @@ Changelog apps/client/src/app/app.component.html - 76 + 77 apps/client/src/app/pages/about/changelog/changelog-page.html @@ -1614,7 +1618,7 @@ Lizenz apps/client/src/app/app.component.html - 87 + 88 apps/client/src/app/pages/about/license/license-page.html @@ -1646,7 +1650,7 @@ Bitte gebe deinen Gutscheincode ein: apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 115 + 124 @@ -1654,7 +1658,7 @@ Gutscheincode konnte nicht eingelöst werden apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 125 + 134 @@ -1662,7 +1666,7 @@ Gutscheincode wurde eingelöst apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 137 + 146 @@ -1670,7 +1674,7 @@ Neu laden apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 138 + 147 @@ -1710,7 +1714,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 264 + 274 @@ -1718,7 +1722,7 @@ Premium ausprobieren apps/client/src/app/components/user-account-membership/user-account-membership.html - 41 + 51 @@ -1726,7 +1730,7 @@ Gutschein einlösen apps/client/src/app/components/user-account-membership/user-account-membership.html - 55 + 65 @@ -2145,14 +2149,6 @@ 214 - - FIRE - FIRE - - apps/client/src/app/pages/portfolio/fire/fire-page-routing.module.ts - 13 - - FIRE FIRE @@ -2390,7 +2386,7 @@ apps/client/src/app/pages/portfolio/portfolio-page-routing.module.ts - 41 + 46 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -2618,7 +2614,7 @@ Registrieren apps/client/src/app/components/header/header.component.html - 407 + 422 @@ -3226,7 +3222,7 @@ Community apps/client/src/app/app.component.html - 120 + 121 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -3396,6 +3392,10 @@ apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html 32 + + libs/ui/src/lib/assistant/assistant.html + 127 + Load Dividends @@ -3522,7 +3522,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 203 + 213 @@ -3538,7 +3538,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 211 + 221 @@ -3554,7 +3554,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 215 + 225 @@ -3570,7 +3570,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 242 + 252 @@ -3586,7 +3586,7 @@ Abonnement abschliessen apps/client/src/app/components/header/header.component.html - 180 + 185 apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html @@ -3598,7 +3598,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 278 + 288 @@ -3618,11 +3618,11 @@ apps/client/src/app/pages/pricing/pricing-page.html - 129 + 134 apps/client/src/app/pages/pricing/pricing-page.html - 191 + 201 @@ -3634,11 +3634,11 @@ apps/client/src/app/pages/pricing/pricing-page.html - 133 + 138 apps/client/src/app/pages/pricing/pricing-page.html - 195 + 205 @@ -3650,11 +3650,11 @@ apps/client/src/app/pages/pricing/pricing-page.html - 137 + 142 apps/client/src/app/pages/pricing/pricing-page.html - 199 + 209 @@ -3674,7 +3674,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 153 + 158 @@ -3682,7 +3682,7 @@ Für Einsteiger, die gerade mit dem Börsenhandel beginnen. apps/client/src/app/pages/pricing/pricing-page.html - 123 + 128 @@ -3690,11 +3690,11 @@ Vollständig verwaltetes Ghostfolio Cloud-Angebot. apps/client/src/app/pages/pricing/pricing-page.html - 152 + 157 apps/client/src/app/pages/pricing/pricing-page.html - 251 + 261 @@ -3702,7 +3702,7 @@ Für ambitionierte Anleger, die den vollständigen Überblick über ihr Anlagevermögen benötigen. apps/client/src/app/pages/pricing/pricing-page.html - 184 + 194 @@ -3710,7 +3710,7 @@ Einmalige Zahlung, keine automatische Erneuerung. apps/client/src/app/pages/pricing/pricing-page.html - 288 + 298 @@ -3726,7 +3726,7 @@ Es ist kostenlos. apps/client/src/app/pages/pricing/pricing-page.html - 309 + 327 @@ -3741,8 +3741,8 @@ 84 - apps/client/src/app/pages/portfolio/fire/fire-page.html - 198 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 88 @@ -3762,7 +3762,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 207 + 217 @@ -3782,11 +3782,11 @@ apps/client/src/app/pages/pricing/pricing-page.html - 141 + 146 apps/client/src/app/pages/pricing/pricing-page.html - 219 + 229 @@ -3810,7 +3810,7 @@ E-Mail und Chat Support apps/client/src/app/pages/pricing/pricing-page.html - 247 + 257 @@ -3870,7 +3870,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 230 + 240 @@ -3886,7 +3886,7 @@ Abonnement erneuern apps/client/src/app/components/header/header.component.html - 186 + 191 apps/client/src/app/components/user-account-membership/user-account-membership.html @@ -3894,7 +3894,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 284 + 294 @@ -4130,7 +4130,7 @@ Private Finanzen apps/client/src/app/app.component.html - 56 + 57 @@ -4138,7 +4138,7 @@ Häufig gestellte Fragen (FAQ) apps/client/src/app/app.component.html - 82 + 83 apps/client/src/app/pages/about/overview/about-overview-page.html @@ -4922,7 +4922,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 306 + 324 @@ -5191,7 +5191,7 @@ snake-case apps/client/src/app/app.component.ts - 76 + 77 apps/client/src/app/core/paths.ts @@ -5224,15 +5224,15 @@ snake-case apps/client/src/app/app.component.ts - 77 + 78 apps/client/src/app/components/header/header.component.ts - 81 + 82 apps/client/src/app/components/header/header.component.ts - 86 + 87 apps/client/src/app/core/paths.ts @@ -5270,13 +5270,17 @@ apps/client/src/app/pages/blog/2023/11/hacktoberfest-2023-debriefing/hacktoberfest-2023-debriefing-page.component.ts 14 + + apps/client/src/app/pages/blog/2024/11/black-weeks-2024/black-weeks-2024-page.component.ts + 15 + apps/client/src/app/pages/faq/overview/faq-overview-page.component.ts 14 apps/client/src/app/pages/pricing/pricing-page.component.ts - 39 + 41 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -5289,27 +5293,27 @@ snake-case apps/client/src/app/app.component.ts - 63 + 64 apps/client/src/app/app.component.ts - 65 + 66 apps/client/src/app/app.component.ts - 69 + 70 apps/client/src/app/app.component.ts - 73 + 74 apps/client/src/app/components/header/header.component.ts - 80 + 81 apps/client/src/app/components/header/header.component.ts - 85 + 86 apps/client/src/app/core/paths.ts @@ -5378,7 +5382,7 @@ snake-case apps/client/src/app/app.component.ts - 74 + 75 apps/client/src/app/core/paths.ts @@ -5395,7 +5399,7 @@ snake-case apps/client/src/app/app.component.ts - 70 + 71 apps/client/src/app/core/paths.ts @@ -5412,15 +5416,15 @@ snake-case apps/client/src/app/app.component.ts - 78 + 79 apps/client/src/app/components/header/header.component.ts - 82 + 83 apps/client/src/app/components/header/header.component.ts - 87 + 88 apps/client/src/app/core/paths.ts @@ -5453,7 +5457,7 @@ snake-case apps/client/src/app/app.component.ts - 79 + 80 apps/client/src/app/components/admin-settings/admin-settings.component.ts @@ -5461,11 +5465,11 @@ apps/client/src/app/components/header/header.component.ts - 83 + 84 apps/client/src/app/components/header/header.component.ts - 88 + 89 apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.component.ts @@ -5473,7 +5477,7 @@ apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 38 + 40 apps/client/src/app/core/http-response.interceptor.ts @@ -5511,6 +5515,10 @@ apps/client/src/app/pages/blog/2023/11/black-week-2023/black-week-2023-page.component.ts 16 + + apps/client/src/app/pages/blog/2024/11/black-weeks-2024/black-weeks-2024-page.component.ts + 16 + apps/client/src/app/pages/faq/saas/saas-page.component.ts 15 @@ -5526,11 +5534,11 @@ snake-case apps/client/src/app/app.component.ts - 80 + 81 apps/client/src/app/components/header/header.component.ts - 89 + 90 apps/client/src/app/core/auth.guard.ts @@ -5554,7 +5562,7 @@ apps/client/src/app/pages/pricing/pricing-page.component.ts - 40 + 42 @@ -5563,15 +5571,15 @@ snake-case apps/client/src/app/app.component.ts - 81 + 82 apps/client/src/app/components/header/header.component.ts - 84 + 85 apps/client/src/app/components/header/header.component.ts - 90 + 91 apps/client/src/app/core/paths.ts @@ -5870,28 +5878,20 @@ 10 - - Ghostfolio X-ray uses static analysis to identify potential issues and risks in your portfolio. - Ghostfolio X-ray nutzt statische Analysen, um potenzielle Probleme und Risiken in deinem Portfolio zu identifizieren. - - apps/client/src/app/pages/portfolio/fire/fire-page.html - 111 - - Currency Cluster Risks Währungsklumpenrisiken - apps/client/src/app/pages/portfolio/fire/fire-page.html - 141 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 31 Account Cluster Risks Kontoklumpenrisiken - apps/client/src/app/pages/portfolio/fire/fire-page.html - 160 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 50 @@ -5983,7 +5983,7 @@ Finde Position... libs/ui/src/lib/assistant/assistant.component.ts - 139 + 144 @@ -6351,7 +6351,7 @@ Seit Wochenbeginn libs/ui/src/lib/assistant/assistant.component.ts - 212 + 225 @@ -6359,7 +6359,7 @@ WTD libs/ui/src/lib/assistant/assistant.component.ts - 212 + 225 @@ -6367,7 +6367,7 @@ Seit Monatsbeginn libs/ui/src/lib/assistant/assistant.component.ts - 216 + 229 @@ -6375,7 +6375,7 @@ MTD libs/ui/src/lib/assistant/assistant.component.ts - 216 + 229 @@ -6383,7 +6383,7 @@ Seit Jahresbeginn libs/ui/src/lib/assistant/assistant.component.ts - 220 + 233 @@ -6419,7 +6419,7 @@ Filter zurücksetzen libs/ui/src/lib/assistant/assistant.html - 157 + 185 @@ -6427,7 +6427,7 @@ Jahr libs/ui/src/lib/assistant/assistant.component.ts - 224 + 237 @@ -6435,7 +6435,7 @@ Jahre libs/ui/src/lib/assistant/assistant.component.ts - 246 + 259 @@ -6443,7 +6443,7 @@ Anlageklassen libs/ui/src/lib/assistant/assistant.html - 138 + 166 @@ -6451,7 +6451,7 @@ Filter anwenden libs/ui/src/lib/assistant/assistant.html - 167 + 195 @@ -6727,7 +6727,7 @@ Tabelle apps/client/src/app/components/home-holdings/home-holdings.html - 17 + 16 @@ -6735,7 +6735,7 @@ Diagramm apps/client/src/app/components/home-holdings/home-holdings.html - 20 + 19 @@ -7030,8 +7030,8 @@ Inactive Inaktiv - apps/client/src/app/pages/portfolio/fire/fire-page.html - 217 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 107 @@ -7092,18 +7092,18 @@ Threshold Min - Threshold Min + Schwellenwert (Minimum) apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 9 + 54 Threshold Max - Threshold Max + Schwellenwert (Maximum) apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 44 + 92 @@ -7111,7 +7111,7 @@ Schliessen apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 77 + 129 @@ -7127,7 +7127,7 @@ Keine automatische Erneuerung. apps/client/src/app/components/user-account-membership/user-account-membership.html - 62 + 72 @@ -7378,14 +7378,6 @@ 93 - - Allocation Cluster Risks - Allokationsklumpenrisiken - - apps/client/src/app/pages/portfolio/fire/fire-page.html - 179 - - Glossary Lexikon @@ -7436,6 +7428,30 @@ 21 + + Threshold range + Schwellenwertbereich + + apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html + 9 + + + + Ghostfolio X-ray uses static analysis to uncover potential issues and risks in your portfolio. Adjust the rules below and set custom thresholds to align with your personal investment strategy. + Ghostfolio X-ray nutzt statische Analysen, um potenzielle Probleme und Risiken in deinem Portfolio aufzudecken. Passe die untenstehenden Regeln an und lege individuelle Schwellenwerte fest, um sie mit deiner persönlichen Anlagestrategie abzustimmen. + + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 5 + + + + Economic Market Cluster Risks + Wirtschaftsraumklumpenrisiken + + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 69 + + diff --git a/apps/client/src/locales/messages.es.xlf b/apps/client/src/locales/messages.es.xlf index 74664782..70029fcc 100644 --- a/apps/client/src/locales/messages.es.xlf +++ b/apps/client/src/locales/messages.es.xlf @@ -23,7 +23,7 @@ El riesgo de pérdida en trading puede ser sustancial. No es aconsejable invertir dinero que puedas necesitar a corto plazo. apps/client/src/app/app.component.html - 199 + 200 @@ -555,7 +555,7 @@ apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 83 + 135 apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html @@ -791,7 +791,7 @@ apps/client/src/app/components/header/header.component.html - 224 + 229 @@ -835,7 +835,7 @@ apps/client/src/app/components/header/header.component.html - 240 + 245 @@ -847,7 +847,7 @@ apps/client/src/app/components/header/header.component.html - 250 + 255 @@ -867,7 +867,7 @@ apps/client/src/app/components/header/header.component.html - 258 + 263 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -891,7 +891,7 @@ apps/client/src/app/components/header/header.component.html - 274 + 279 @@ -899,7 +899,7 @@ Recursos apps/client/src/app/app.component.html - 63 + 64 apps/client/src/app/components/header/header.component.html @@ -907,7 +907,7 @@ apps/client/src/app/components/header/header.component.html - 286 + 291 apps/client/src/app/pages/resources/overview/resources-overview.component.html @@ -919,19 +919,19 @@ Precios apps/client/src/app/app.component.html - 96 + 97 apps/client/src/app/components/header/header.component.html - 98 + 99 apps/client/src/app/components/header/header.component.html - 297 + 303 apps/client/src/app/components/header/header.component.html - 368 + 379 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -943,15 +943,15 @@ Sobre apps/client/src/app/app.component.html - 69 + 70 apps/client/src/app/components/header/header.component.html - 112 + 117 apps/client/src/app/components/header/header.component.html - 354 + 364 @@ -959,7 +959,7 @@ apps/client/src/app/components/header/header.component.html - 206 + 211 @@ -967,7 +967,7 @@ Mi Ghostfolio apps/client/src/app/components/header/header.component.html - 265 + 270 @@ -975,7 +975,7 @@ Sobre Ghostfolio apps/client/src/app/components/header/header.component.html - 306 + 316 apps/client/src/app/pages/about/overview/about-overview-page.html @@ -987,11 +987,11 @@ Funcionalidades apps/client/src/app/app.component.html - 78 + 79 apps/client/src/app/components/header/header.component.html - 341 + 351 apps/client/src/app/pages/features/features-page.html @@ -1003,11 +1003,11 @@ Mercados apps/client/src/app/app.component.html - 60 + 61 apps/client/src/app/components/header/header.component.html - 383 + 398 apps/client/src/app/components/home-market/home-market.html @@ -1039,7 +1039,7 @@ apps/client/src/app/components/header/header.component.ts - 229 + 230 @@ -1047,7 +1047,7 @@ Vaya! Token de seguridad incorrecto. apps/client/src/app/components/header/header.component.ts - 244 + 245 apps/client/src/app/components/user-account-settings/user-account-settings.component.ts @@ -1059,7 +1059,7 @@ Gestión de las operaciones apps/client/src/app/components/home-holdings/home-holdings.html - 65 + 63 @@ -1151,7 +1151,7 @@ Iniciar sesión apps/client/src/app/components/header/header.component.html - 397 + 412 apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html @@ -1246,8 +1246,8 @@ 89 - apps/client/src/app/pages/portfolio/fire/fire-page.html - 122 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 12 @@ -1363,7 +1363,7 @@ libs/ui/src/lib/assistant/assistant.html - 127 + 155 @@ -1415,7 +1415,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 210 + 223 @@ -1427,7 +1427,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 220 + 233 @@ -1439,7 +1439,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 224 + 237 @@ -1451,7 +1451,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 246 + 259 @@ -1463,7 +1463,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 249 + 262 @@ -1499,7 +1499,7 @@ Política de privacidad apps/client/src/app/app.component.html - 102 + 103 apps/client/src/app/pages/about/privacy-policy/privacy-policy-page.html @@ -1511,7 +1511,7 @@ Blog apps/client/src/app/app.component.html - 72 + 73 apps/client/src/app/pages/blog/2021/07/hallo-ghostfolio/hallo-ghostfolio-page.html @@ -1593,6 +1593,10 @@ apps/client/src/app/pages/blog/2024/09/hacktoberfest-2024/hacktoberfest-2024-page.html 187 + + apps/client/src/app/pages/blog/2024/11/black-weeks-2024/black-weeks-2024-page.html + 167 + apps/client/src/app/pages/blog/blog-page.html 5 @@ -1603,7 +1607,7 @@ Registro de cambios apps/client/src/app/app.component.html - 76 + 77 apps/client/src/app/pages/about/changelog/changelog-page.html @@ -1615,7 +1619,7 @@ Licencia de uso apps/client/src/app/app.component.html - 87 + 88 apps/client/src/app/pages/about/license/license-page.html @@ -1647,7 +1651,7 @@ Por favor, ingresa tu código de cupón: apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 115 + 124 @@ -1655,7 +1659,7 @@ No se puede canjear este código de cupón apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 125 + 134 @@ -1663,7 +1667,7 @@ El codigo de cupón ha sido canjeado apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 137 + 146 @@ -1671,7 +1675,7 @@ Refrescar apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 138 + 147 @@ -1711,7 +1715,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 264 + 274 @@ -1719,7 +1723,7 @@ Prueba Premium apps/client/src/app/components/user-account-membership/user-account-membership.html - 41 + 51 @@ -1727,7 +1731,7 @@ Canjea el cupón apps/client/src/app/components/user-account-membership/user-account-membership.html - 55 + 65 @@ -2146,14 +2150,6 @@ 214 - - FIRE - FIRE - - apps/client/src/app/pages/portfolio/fire/fire-page-routing.module.ts - 13 - - FIRE FIRE @@ -2391,7 +2387,7 @@ apps/client/src/app/pages/portfolio/portfolio-page-routing.module.ts - 41 + 46 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -2619,7 +2615,7 @@ Comenzar apps/client/src/app/components/header/header.component.html - 407 + 422 @@ -3227,7 +3223,7 @@ Comunidad apps/client/src/app/app.component.html - 120 + 121 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -3397,6 +3393,10 @@ apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html 32 + + libs/ui/src/lib/assistant/assistant.html + 127 + Load Dividends @@ -3523,7 +3523,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 203 + 213 @@ -3539,7 +3539,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 211 + 221 @@ -3555,7 +3555,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 215 + 225 @@ -3571,7 +3571,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 242 + 252 @@ -3587,7 +3587,7 @@ Mejorar plan apps/client/src/app/components/header/header.component.html - 180 + 185 apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html @@ -3599,7 +3599,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 278 + 288 @@ -3619,11 +3619,11 @@ apps/client/src/app/pages/pricing/pricing-page.html - 129 + 134 apps/client/src/app/pages/pricing/pricing-page.html - 191 + 201 @@ -3635,11 +3635,11 @@ apps/client/src/app/pages/pricing/pricing-page.html - 133 + 138 apps/client/src/app/pages/pricing/pricing-page.html - 195 + 205 @@ -3651,11 +3651,11 @@ apps/client/src/app/pages/pricing/pricing-page.html - 137 + 142 apps/client/src/app/pages/pricing/pricing-page.html - 199 + 209 @@ -3675,7 +3675,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 153 + 158 @@ -3683,7 +3683,7 @@ Para nuevos inversores que estan empezando con el trading. apps/client/src/app/pages/pricing/pricing-page.html - 123 + 128 @@ -3691,11 +3691,11 @@ Oferta en la nube de Ghostfolio totalmente administrada. apps/client/src/app/pages/pricing/pricing-page.html - 152 + 157 apps/client/src/app/pages/pricing/pricing-page.html - 251 + 261 @@ -3703,7 +3703,7 @@ Para inversores ambiciosos que necesitan una visión completa de sus activos financieros apps/client/src/app/pages/pricing/pricing-page.html - 184 + 194 @@ -3711,7 +3711,7 @@ Pago único, sin renovación automática. apps/client/src/app/pages/pricing/pricing-page.html - 288 + 298 @@ -3727,7 +3727,7 @@ Es gratis. apps/client/src/app/pages/pricing/pricing-page.html - 309 + 327 @@ -3742,8 +3742,8 @@ 84 - apps/client/src/app/pages/portfolio/fire/fire-page.html - 198 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 88 @@ -3763,7 +3763,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 207 + 217 @@ -3783,11 +3783,11 @@ apps/client/src/app/pages/pricing/pricing-page.html - 141 + 146 apps/client/src/app/pages/pricing/pricing-page.html - 219 + 229 @@ -3811,7 +3811,7 @@ Soporte a Traves de Email y Chat apps/client/src/app/pages/pricing/pricing-page.html - 247 + 257 @@ -3871,7 +3871,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 230 + 240 @@ -3887,7 +3887,7 @@ Renovar Plan apps/client/src/app/components/header/header.component.html - 186 + 191 apps/client/src/app/components/user-account-membership/user-account-membership.html @@ -3895,7 +3895,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 284 + 294 @@ -4131,7 +4131,7 @@ Personal Finance apps/client/src/app/app.component.html - 56 + 57 @@ -4139,7 +4139,7 @@ Frequently Asked Questions (FAQ) apps/client/src/app/app.component.html - 82 + 83 apps/client/src/app/pages/about/overview/about-overview-page.html @@ -4923,7 +4923,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 306 + 324 @@ -5192,7 +5192,7 @@ snake-case apps/client/src/app/app.component.ts - 76 + 77 apps/client/src/app/core/paths.ts @@ -5225,15 +5225,15 @@ snake-case apps/client/src/app/app.component.ts - 77 + 78 apps/client/src/app/components/header/header.component.ts - 81 + 82 apps/client/src/app/components/header/header.component.ts - 86 + 87 apps/client/src/app/core/paths.ts @@ -5271,13 +5271,17 @@ apps/client/src/app/pages/blog/2023/11/hacktoberfest-2023-debriefing/hacktoberfest-2023-debriefing-page.component.ts 14 + + apps/client/src/app/pages/blog/2024/11/black-weeks-2024/black-weeks-2024-page.component.ts + 15 + apps/client/src/app/pages/faq/overview/faq-overview-page.component.ts 14 apps/client/src/app/pages/pricing/pricing-page.component.ts - 39 + 41 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -5290,27 +5294,27 @@ snake-case apps/client/src/app/app.component.ts - 63 + 64 apps/client/src/app/app.component.ts - 65 + 66 apps/client/src/app/app.component.ts - 69 + 70 apps/client/src/app/app.component.ts - 73 + 74 apps/client/src/app/components/header/header.component.ts - 80 + 81 apps/client/src/app/components/header/header.component.ts - 85 + 86 apps/client/src/app/core/paths.ts @@ -5379,7 +5383,7 @@ snake-case apps/client/src/app/app.component.ts - 74 + 75 apps/client/src/app/core/paths.ts @@ -5396,7 +5400,7 @@ snake-case apps/client/src/app/app.component.ts - 70 + 71 apps/client/src/app/core/paths.ts @@ -5413,15 +5417,15 @@ snake-case apps/client/src/app/app.component.ts - 78 + 79 apps/client/src/app/components/header/header.component.ts - 82 + 83 apps/client/src/app/components/header/header.component.ts - 87 + 88 apps/client/src/app/core/paths.ts @@ -5454,7 +5458,7 @@ snake-case apps/client/src/app/app.component.ts - 79 + 80 apps/client/src/app/components/admin-settings/admin-settings.component.ts @@ -5462,11 +5466,11 @@ apps/client/src/app/components/header/header.component.ts - 83 + 84 apps/client/src/app/components/header/header.component.ts - 88 + 89 apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.component.ts @@ -5474,7 +5478,7 @@ apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 38 + 40 apps/client/src/app/core/http-response.interceptor.ts @@ -5512,6 +5516,10 @@ apps/client/src/app/pages/blog/2023/11/black-week-2023/black-week-2023-page.component.ts 16 + + apps/client/src/app/pages/blog/2024/11/black-weeks-2024/black-weeks-2024-page.component.ts + 16 + apps/client/src/app/pages/faq/saas/saas-page.component.ts 15 @@ -5527,11 +5535,11 @@ snake-case apps/client/src/app/app.component.ts - 80 + 81 apps/client/src/app/components/header/header.component.ts - 89 + 90 apps/client/src/app/core/auth.guard.ts @@ -5555,7 +5563,7 @@ apps/client/src/app/pages/pricing/pricing-page.component.ts - 40 + 42 @@ -5564,15 +5572,15 @@ snake-case apps/client/src/app/app.component.ts - 81 + 82 apps/client/src/app/components/header/header.component.ts - 84 + 85 apps/client/src/app/components/header/header.component.ts - 90 + 91 apps/client/src/app/core/paths.ts @@ -5871,28 +5879,20 @@ 10 - - Ghostfolio X-ray uses static analysis to identify potential issues and risks in your portfolio. - Ghostfolio X-ray uses static analysis to identify potential issues and risks in your portfolio. - - apps/client/src/app/pages/portfolio/fire/fire-page.html - 111 - - Currency Cluster Risks Currency Cluster Risks - apps/client/src/app/pages/portfolio/fire/fire-page.html - 141 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 31 Account Cluster Risks Account Cluster Risks - apps/client/src/app/pages/portfolio/fire/fire-page.html - 160 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 50 @@ -5984,7 +5984,7 @@ Find holding... libs/ui/src/lib/assistant/assistant.component.ts - 139 + 144 @@ -6352,7 +6352,7 @@ Week to date libs/ui/src/lib/assistant/assistant.component.ts - 212 + 225 @@ -6360,7 +6360,7 @@ WTD libs/ui/src/lib/assistant/assistant.component.ts - 212 + 225 @@ -6368,7 +6368,7 @@ Month to date libs/ui/src/lib/assistant/assistant.component.ts - 216 + 229 @@ -6376,7 +6376,7 @@ MTD libs/ui/src/lib/assistant/assistant.component.ts - 216 + 229 @@ -6384,7 +6384,7 @@ Year to date libs/ui/src/lib/assistant/assistant.component.ts - 220 + 233 @@ -6420,7 +6420,7 @@ Reiniciar filtros libs/ui/src/lib/assistant/assistant.html - 157 + 185 @@ -6428,7 +6428,7 @@ año libs/ui/src/lib/assistant/assistant.component.ts - 224 + 237 @@ -6436,7 +6436,7 @@ años libs/ui/src/lib/assistant/assistant.component.ts - 246 + 259 @@ -6444,7 +6444,7 @@ Asset Classes libs/ui/src/lib/assistant/assistant.html - 138 + 166 @@ -6452,7 +6452,7 @@ Aplicar filtros libs/ui/src/lib/assistant/assistant.html - 167 + 195 @@ -6728,7 +6728,7 @@ Tabla apps/client/src/app/components/home-holdings/home-holdings.html - 17 + 16 @@ -6736,7 +6736,7 @@ Grafico apps/client/src/app/components/home-holdings/home-holdings.html - 20 + 19 @@ -7031,8 +7031,8 @@ Inactive Inactive - apps/client/src/app/pages/portfolio/fire/fire-page.html - 217 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 107 @@ -7096,7 +7096,7 @@ Threshold Min apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 9 + 54 @@ -7104,7 +7104,7 @@ Threshold Max apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 44 + 92 @@ -7112,7 +7112,7 @@ Close apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 77 + 129 @@ -7128,7 +7128,7 @@ No auto-renewal. apps/client/src/app/components/user-account-membership/user-account-membership.html - 62 + 72 @@ -7379,14 +7379,6 @@ 93 - - Allocation Cluster Risks - Allocation Cluster Risks - - apps/client/src/app/pages/portfolio/fire/fire-page.html - 179 - - Glossary Glossary @@ -7437,6 +7429,30 @@ 21 + + Threshold range + Threshold range + + apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html + 9 + + + + Ghostfolio X-ray uses static analysis to uncover potential issues and risks in your portfolio. Adjust the rules below and set custom thresholds to align with your personal investment strategy. + Ghostfolio X-ray uses static analysis to uncover potential issues and risks in your portfolio. Adjust the rules below and set custom thresholds to align with your personal investment strategy. + + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 5 + + + + Economic Market Cluster Risks + Economic Market Cluster Risks + + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 69 + + diff --git a/apps/client/src/locales/messages.fr.xlf b/apps/client/src/locales/messages.fr.xlf index bf8efff8..b442e461 100644 --- a/apps/client/src/locales/messages.fr.xlf +++ b/apps/client/src/locales/messages.fr.xlf @@ -6,7 +6,7 @@ Le risque de perte en investissant peut être important. Il est déconseillé d’investir de l’argent dont vous pourriez avoir besoin à court terme. apps/client/src/app/app.component.html - 199 + 200 @@ -614,7 +614,7 @@ apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 83 + 135 apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html @@ -970,7 +970,7 @@ libs/ui/src/lib/assistant/assistant.html - 127 + 155 @@ -1058,7 +1058,7 @@ apps/client/src/app/components/header/header.component.html - 224 + 229 @@ -1086,7 +1086,7 @@ apps/client/src/app/components/header/header.component.html - 258 + 263 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1146,7 +1146,7 @@ apps/client/src/app/pages/portfolio/portfolio-page-routing.module.ts - 41 + 46 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -1178,7 +1178,7 @@ apps/client/src/app/components/header/header.component.html - 240 + 245 @@ -1190,7 +1190,7 @@ apps/client/src/app/components/header/header.component.html - 250 + 255 @@ -1202,7 +1202,7 @@ apps/client/src/app/components/header/header.component.html - 274 + 279 @@ -1210,7 +1210,7 @@ Ressources apps/client/src/app/app.component.html - 63 + 64 apps/client/src/app/components/header/header.component.html @@ -1218,7 +1218,7 @@ apps/client/src/app/components/header/header.component.html - 286 + 291 apps/client/src/app/pages/resources/overview/resources-overview.component.html @@ -1230,19 +1230,19 @@ Prix apps/client/src/app/app.component.html - 96 + 97 apps/client/src/app/components/header/header.component.html - 98 + 99 apps/client/src/app/components/header/header.component.html - 297 + 303 apps/client/src/app/components/header/header.component.html - 368 + 379 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -1254,15 +1254,15 @@ À propos apps/client/src/app/app.component.html - 69 + 70 apps/client/src/app/components/header/header.component.html - 112 + 117 apps/client/src/app/components/header/header.component.html - 354 + 364 @@ -1270,7 +1270,7 @@ Moi apps/client/src/app/components/header/header.component.html - 206 + 211 @@ -1278,7 +1278,7 @@ Mon Ghostfolio apps/client/src/app/components/header/header.component.html - 265 + 270 @@ -1286,7 +1286,7 @@ À propos de Ghostfolio apps/client/src/app/components/header/header.component.html - 306 + 316 apps/client/src/app/pages/about/overview/about-overview-page.html @@ -1298,11 +1298,11 @@ Fonctionnalités apps/client/src/app/app.component.html - 78 + 79 apps/client/src/app/components/header/header.component.html - 341 + 351 apps/client/src/app/pages/features/features-page.html @@ -1314,11 +1314,11 @@ Marchés apps/client/src/app/app.component.html - 60 + 61 apps/client/src/app/components/header/header.component.html - 383 + 398 apps/client/src/app/components/home-market/home-market.html @@ -1334,7 +1334,7 @@ Se connecter apps/client/src/app/components/header/header.component.html - 397 + 412 apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html @@ -1346,7 +1346,7 @@ Démarrer apps/client/src/app/components/header/header.component.html - 407 + 422 @@ -1358,7 +1358,7 @@ apps/client/src/app/components/header/header.component.ts - 229 + 230 @@ -1366,7 +1366,7 @@ Oups! Jeton de Sécurité Incorrect. apps/client/src/app/components/header/header.component.ts - 244 + 245 apps/client/src/app/components/user-account-settings/user-account-settings.component.ts @@ -1378,7 +1378,7 @@ Gérer les Activités apps/client/src/app/components/home-holdings/home-holdings.html - 65 + 63 @@ -1593,8 +1593,8 @@ 89 - apps/client/src/app/pages/portfolio/fire/fire-page.html - 122 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 12 @@ -1726,7 +1726,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 210 + 223 @@ -1738,7 +1738,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 220 + 233 @@ -1750,7 +1750,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 224 + 237 @@ -1762,7 +1762,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 246 + 259 @@ -1774,7 +1774,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 249 + 262 @@ -1846,7 +1846,7 @@ Historique des modifications apps/client/src/app/app.component.html - 76 + 77 apps/client/src/app/pages/about/changelog/changelog-page.html @@ -1858,7 +1858,7 @@ License apps/client/src/app/app.component.html - 87 + 88 apps/client/src/app/pages/about/license/license-page.html @@ -1882,7 +1882,7 @@ Politique de Vie Privée apps/client/src/app/app.component.html - 102 + 103 apps/client/src/app/pages/about/privacy-policy/privacy-policy-page.html @@ -1910,7 +1910,7 @@ Veuillez entrer votre code promotionnel : apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 115 + 124 @@ -1918,7 +1918,7 @@ Le code promotionnel n’a pas pu être appliqué apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 125 + 134 @@ -1926,7 +1926,7 @@ Le code promotionnel a été appliqué apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 137 + 146 @@ -1934,7 +1934,7 @@ Rafraîchir apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 138 + 147 @@ -1974,7 +1974,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 264 + 274 @@ -1982,7 +1982,7 @@ Essayer Premium apps/client/src/app/components/user-account-membership/user-account-membership.html - 41 + 51 @@ -1990,7 +1990,7 @@ Utiliser un Code Promotionnel apps/client/src/app/components/user-account-membership/user-account-membership.html - 55 + 65 @@ -2022,7 +2022,7 @@ Communauté apps/client/src/app/app.component.html - 120 + 121 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -2262,7 +2262,7 @@ Blog apps/client/src/app/app.component.html - 72 + 73 apps/client/src/app/pages/blog/2021/07/hallo-ghostfolio/hallo-ghostfolio-page.html @@ -2344,6 +2344,10 @@ apps/client/src/app/pages/blog/2024/09/hacktoberfest-2024/hacktoberfest-2024-page.html 187 + + apps/client/src/app/pages/blog/2024/11/black-weeks-2024/black-weeks-2024-page.html + 167 + apps/client/src/app/pages/blog/blog-page.html 5 @@ -2797,14 +2801,6 @@ 351 - - FIRE - FIRE - - apps/client/src/app/pages/portfolio/fire/fire-page-routing.module.ts - 13 - - FIRE FIRE @@ -3396,6 +3392,10 @@ apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html 32 + + libs/ui/src/lib/assistant/assistant.html + 127 + Load Dividends @@ -3522,7 +3522,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 203 + 213 @@ -3538,7 +3538,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 211 + 221 @@ -3554,7 +3554,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 215 + 225 @@ -3570,7 +3570,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 242 + 252 @@ -3586,7 +3586,7 @@ Mettre à niveau l’Abonnement apps/client/src/app/components/header/header.component.html - 180 + 185 apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html @@ -3598,7 +3598,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 278 + 288 @@ -3618,11 +3618,11 @@ apps/client/src/app/pages/pricing/pricing-page.html - 129 + 134 apps/client/src/app/pages/pricing/pricing-page.html - 191 + 201 @@ -3634,11 +3634,11 @@ apps/client/src/app/pages/pricing/pricing-page.html - 133 + 138 apps/client/src/app/pages/pricing/pricing-page.html - 195 + 205 @@ -3650,11 +3650,11 @@ apps/client/src/app/pages/pricing/pricing-page.html - 137 + 142 apps/client/src/app/pages/pricing/pricing-page.html - 199 + 209 @@ -3674,7 +3674,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 153 + 158 @@ -3682,7 +3682,7 @@ Pour les nouveaux investisseurs qui débutent en Bourse. apps/client/src/app/pages/pricing/pricing-page.html - 123 + 128 @@ -3690,11 +3690,11 @@ Offre Ghostfolio cloud complètement administrée. apps/client/src/app/pages/pricing/pricing-page.html - 152 + 157 apps/client/src/app/pages/pricing/pricing-page.html - 251 + 261 @@ -3702,7 +3702,7 @@ Pour les investisseurs ambitieux qui ont besoin d’une vue complète de leurs actifs financiers. apps/client/src/app/pages/pricing/pricing-page.html - 184 + 194 @@ -3710,7 +3710,7 @@ Paiement unique, sans auto-renouvellement. apps/client/src/app/pages/pricing/pricing-page.html - 288 + 298 @@ -3726,7 +3726,7 @@ C’est gratuit. apps/client/src/app/pages/pricing/pricing-page.html - 309 + 327 @@ -3741,8 +3741,8 @@ 84 - apps/client/src/app/pages/portfolio/fire/fire-page.html - 198 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 88 @@ -3762,7 +3762,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 207 + 217 @@ -3782,11 +3782,11 @@ apps/client/src/app/pages/pricing/pricing-page.html - 141 + 146 apps/client/src/app/pages/pricing/pricing-page.html - 219 + 229 @@ -3810,7 +3810,7 @@ Support par E-mail et Tchat apps/client/src/app/pages/pricing/pricing-page.html - 247 + 257 @@ -3870,7 +3870,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 230 + 240 @@ -3886,7 +3886,7 @@ Renouveler l’Abonnement apps/client/src/app/components/header/header.component.html - 186 + 191 apps/client/src/app/components/user-account-membership/user-account-membership.html @@ -3894,7 +3894,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 284 + 294 @@ -4130,7 +4130,7 @@ Finance Personnelle apps/client/src/app/app.component.html - 56 + 57 @@ -4138,7 +4138,7 @@ Questions Fréquentes (FAQ) apps/client/src/app/app.component.html - 82 + 83 apps/client/src/app/pages/about/overview/about-overview-page.html @@ -4922,7 +4922,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 306 + 324 @@ -5191,7 +5191,7 @@ snake-case apps/client/src/app/app.component.ts - 76 + 77 apps/client/src/app/core/paths.ts @@ -5224,15 +5224,15 @@ snake-case apps/client/src/app/app.component.ts - 77 + 78 apps/client/src/app/components/header/header.component.ts - 81 + 82 apps/client/src/app/components/header/header.component.ts - 86 + 87 apps/client/src/app/core/paths.ts @@ -5270,13 +5270,17 @@ apps/client/src/app/pages/blog/2023/11/hacktoberfest-2023-debriefing/hacktoberfest-2023-debriefing-page.component.ts 14 + + apps/client/src/app/pages/blog/2024/11/black-weeks-2024/black-weeks-2024-page.component.ts + 15 + apps/client/src/app/pages/faq/overview/faq-overview-page.component.ts 14 apps/client/src/app/pages/pricing/pricing-page.component.ts - 39 + 41 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -5289,27 +5293,27 @@ snake-case apps/client/src/app/app.component.ts - 63 + 64 apps/client/src/app/app.component.ts - 65 + 66 apps/client/src/app/app.component.ts - 69 + 70 apps/client/src/app/app.component.ts - 73 + 74 apps/client/src/app/components/header/header.component.ts - 80 + 81 apps/client/src/app/components/header/header.component.ts - 85 + 86 apps/client/src/app/core/paths.ts @@ -5378,7 +5382,7 @@ snake-case apps/client/src/app/app.component.ts - 74 + 75 apps/client/src/app/core/paths.ts @@ -5395,7 +5399,7 @@ snake-case apps/client/src/app/app.component.ts - 70 + 71 apps/client/src/app/core/paths.ts @@ -5412,15 +5416,15 @@ snake-case apps/client/src/app/app.component.ts - 78 + 79 apps/client/src/app/components/header/header.component.ts - 82 + 83 apps/client/src/app/components/header/header.component.ts - 87 + 88 apps/client/src/app/core/paths.ts @@ -5453,7 +5457,7 @@ snake-case apps/client/src/app/app.component.ts - 79 + 80 apps/client/src/app/components/admin-settings/admin-settings.component.ts @@ -5461,11 +5465,11 @@ apps/client/src/app/components/header/header.component.ts - 83 + 84 apps/client/src/app/components/header/header.component.ts - 88 + 89 apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.component.ts @@ -5473,7 +5477,7 @@ apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 38 + 40 apps/client/src/app/core/http-response.interceptor.ts @@ -5511,6 +5515,10 @@ apps/client/src/app/pages/blog/2023/11/black-week-2023/black-week-2023-page.component.ts 16 + + apps/client/src/app/pages/blog/2024/11/black-weeks-2024/black-weeks-2024-page.component.ts + 16 + apps/client/src/app/pages/faq/saas/saas-page.component.ts 15 @@ -5526,11 +5534,11 @@ snake-case apps/client/src/app/app.component.ts - 80 + 81 apps/client/src/app/components/header/header.component.ts - 89 + 90 apps/client/src/app/core/auth.guard.ts @@ -5554,7 +5562,7 @@ apps/client/src/app/pages/pricing/pricing-page.component.ts - 40 + 42 @@ -5563,15 +5571,15 @@ snake-case apps/client/src/app/app.component.ts - 81 + 82 apps/client/src/app/components/header/header.component.ts - 84 + 85 apps/client/src/app/components/header/header.component.ts - 90 + 91 apps/client/src/app/core/paths.ts @@ -5870,28 +5878,20 @@ 10 - - Ghostfolio X-ray uses static analysis to identify potential issues and risks in your portfolio. - Ghostfolio X-ray utilise l'analyse statique pour identifier de potentiels problèmes et risques dans votre portefeuille. - - apps/client/src/app/pages/portfolio/fire/fire-page.html - 111 - - Currency Cluster Risks Risques de change - apps/client/src/app/pages/portfolio/fire/fire-page.html - 141 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 31 Account Cluster Risks Risques liés aux regroupements de comptes - apps/client/src/app/pages/portfolio/fire/fire-page.html - 160 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 50 @@ -5983,7 +5983,7 @@ Chercher un actif... libs/ui/src/lib/assistant/assistant.component.ts - 139 + 144 @@ -6351,7 +6351,7 @@ Week to date libs/ui/src/lib/assistant/assistant.component.ts - 212 + 225 @@ -6359,7 +6359,7 @@ WTD libs/ui/src/lib/assistant/assistant.component.ts - 212 + 225 @@ -6367,7 +6367,7 @@ Month to date libs/ui/src/lib/assistant/assistant.component.ts - 216 + 229 @@ -6375,7 +6375,7 @@ MTD libs/ui/src/lib/assistant/assistant.component.ts - 216 + 229 @@ -6383,7 +6383,7 @@ Year to date libs/ui/src/lib/assistant/assistant.component.ts - 220 + 233 @@ -6419,7 +6419,7 @@ Réinitialiser les Filtres libs/ui/src/lib/assistant/assistant.html - 157 + 185 @@ -6427,7 +6427,7 @@ année libs/ui/src/lib/assistant/assistant.component.ts - 224 + 237 @@ -6435,7 +6435,7 @@ années libs/ui/src/lib/assistant/assistant.component.ts - 246 + 259 @@ -6443,7 +6443,7 @@ Types d'Actifs libs/ui/src/lib/assistant/assistant.html - 138 + 166 @@ -6451,7 +6451,7 @@ Appliquer les Filtres libs/ui/src/lib/assistant/assistant.html - 167 + 195 @@ -6727,7 +6727,7 @@ Table apps/client/src/app/components/home-holdings/home-holdings.html - 17 + 16 @@ -6735,7 +6735,7 @@ Tableau apps/client/src/app/components/home-holdings/home-holdings.html - 20 + 19 @@ -7030,8 +7030,8 @@ Inactive Inactif - apps/client/src/app/pages/portfolio/fire/fire-page.html - 217 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 107 @@ -7095,7 +7095,7 @@ Threshold Min apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 9 + 54 @@ -7103,7 +7103,7 @@ Threshold Max apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 44 + 92 @@ -7111,7 +7111,7 @@ Close apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 77 + 129 @@ -7127,7 +7127,7 @@ No auto-renewal. apps/client/src/app/components/user-account-membership/user-account-membership.html - 62 + 72 @@ -7378,14 +7378,6 @@ 93 - - Allocation Cluster Risks - Allocation Cluster Risks - - apps/client/src/app/pages/portfolio/fire/fire-page.html - 179 - - Glossary Glossary @@ -7436,6 +7428,30 @@ 21 + + Threshold range + Threshold range + + apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html + 9 + + + + Ghostfolio X-ray uses static analysis to uncover potential issues and risks in your portfolio. Adjust the rules below and set custom thresholds to align with your personal investment strategy. + Ghostfolio X-ray uses static analysis to uncover potential issues and risks in your portfolio. Adjust the rules below and set custom thresholds to align with your personal investment strategy. + + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 5 + + + + Economic Market Cluster Risks + Economic Market Cluster Risks + + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 69 + + diff --git a/apps/client/src/locales/messages.it.xlf b/apps/client/src/locales/messages.it.xlf index 8f55b327..848e45d1 100644 --- a/apps/client/src/locales/messages.it.xlf +++ b/apps/client/src/locales/messages.it.xlf @@ -23,7 +23,7 @@ Il rischio di perdita nel trading può essere notevole. Non è consigliabile investire denaro di cui potresti avere bisogno a breve termine. apps/client/src/app/app.component.html - 199 + 200 @@ -555,7 +555,7 @@ apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 83 + 135 apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html @@ -791,7 +791,7 @@ apps/client/src/app/components/header/header.component.html - 224 + 229 @@ -835,7 +835,7 @@ apps/client/src/app/components/header/header.component.html - 240 + 245 @@ -847,7 +847,7 @@ apps/client/src/app/components/header/header.component.html - 250 + 255 @@ -867,7 +867,7 @@ apps/client/src/app/components/header/header.component.html - 258 + 263 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -891,7 +891,7 @@ apps/client/src/app/components/header/header.component.html - 274 + 279 @@ -899,7 +899,7 @@ Risorse apps/client/src/app/app.component.html - 63 + 64 apps/client/src/app/components/header/header.component.html @@ -907,7 +907,7 @@ apps/client/src/app/components/header/header.component.html - 286 + 291 apps/client/src/app/pages/resources/overview/resources-overview.component.html @@ -919,19 +919,19 @@ Prezzi apps/client/src/app/app.component.html - 96 + 97 apps/client/src/app/components/header/header.component.html - 98 + 99 apps/client/src/app/components/header/header.component.html - 297 + 303 apps/client/src/app/components/header/header.component.html - 368 + 379 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -943,15 +943,15 @@ Informazioni su apps/client/src/app/app.component.html - 69 + 70 apps/client/src/app/components/header/header.component.html - 112 + 117 apps/client/src/app/components/header/header.component.html - 354 + 364 @@ -959,7 +959,7 @@ Io apps/client/src/app/components/header/header.component.html - 206 + 211 @@ -967,7 +967,7 @@ Il mio Ghostfolio apps/client/src/app/components/header/header.component.html - 265 + 270 @@ -975,7 +975,7 @@ Informazioni su Ghostfolio apps/client/src/app/components/header/header.component.html - 306 + 316 apps/client/src/app/pages/about/overview/about-overview-page.html @@ -987,11 +987,11 @@ Funzionalità apps/client/src/app/app.component.html - 78 + 79 apps/client/src/app/components/header/header.component.html - 341 + 351 apps/client/src/app/pages/features/features-page.html @@ -1003,11 +1003,11 @@ Mercati apps/client/src/app/app.component.html - 60 + 61 apps/client/src/app/components/header/header.component.html - 383 + 398 apps/client/src/app/components/home-market/home-market.html @@ -1039,7 +1039,7 @@ apps/client/src/app/components/header/header.component.ts - 229 + 230 @@ -1047,7 +1047,7 @@ Ops! Token di sicurezza errato. apps/client/src/app/components/header/header.component.ts - 244 + 245 apps/client/src/app/components/user-account-settings/user-account-settings.component.ts @@ -1059,7 +1059,7 @@ Gestione delle attività apps/client/src/app/components/home-holdings/home-holdings.html - 65 + 63 @@ -1151,7 +1151,7 @@ Accedi apps/client/src/app/components/header/header.component.html - 397 + 412 apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html @@ -1246,8 +1246,8 @@ 89 - apps/client/src/app/pages/portfolio/fire/fire-page.html - 122 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 12 @@ -1363,7 +1363,7 @@ libs/ui/src/lib/assistant/assistant.html - 127 + 155 @@ -1415,7 +1415,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 210 + 223 @@ -1427,7 +1427,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 220 + 233 @@ -1439,7 +1439,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 224 + 237 @@ -1451,7 +1451,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 246 + 259 @@ -1463,7 +1463,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 249 + 262 @@ -1499,7 +1499,7 @@ Informativa sulla privacy apps/client/src/app/app.component.html - 102 + 103 apps/client/src/app/pages/about/privacy-policy/privacy-policy-page.html @@ -1511,7 +1511,7 @@ Blog apps/client/src/app/app.component.html - 72 + 73 apps/client/src/app/pages/blog/2021/07/hallo-ghostfolio/hallo-ghostfolio-page.html @@ -1593,6 +1593,10 @@ apps/client/src/app/pages/blog/2024/09/hacktoberfest-2024/hacktoberfest-2024-page.html 187 + + apps/client/src/app/pages/blog/2024/11/black-weeks-2024/black-weeks-2024-page.html + 167 + apps/client/src/app/pages/blog/blog-page.html 5 @@ -1603,7 +1607,7 @@ Registro delle modifiche apps/client/src/app/app.component.html - 76 + 77 apps/client/src/app/pages/about/changelog/changelog-page.html @@ -1615,7 +1619,7 @@ Licenza d’uso apps/client/src/app/app.component.html - 87 + 88 apps/client/src/app/pages/about/license/license-page.html @@ -1647,7 +1651,7 @@ Inserisci il tuo codice del buono: apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 115 + 124 @@ -1655,7 +1659,7 @@ Impossibile riscattare il codice del buono apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 125 + 134 @@ -1663,7 +1667,7 @@ Il codice del buono è stato riscattato apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 137 + 146 @@ -1671,7 +1675,7 @@ Ricarica apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 138 + 147 @@ -1711,7 +1715,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 264 + 274 @@ -1719,7 +1723,7 @@ Prova Premium apps/client/src/app/components/user-account-membership/user-account-membership.html - 41 + 51 @@ -1727,7 +1731,7 @@ Riscatta il buono apps/client/src/app/components/user-account-membership/user-account-membership.html - 55 + 65 @@ -2146,14 +2150,6 @@ 214 - - FIRE - FIRE - - apps/client/src/app/pages/portfolio/fire/fire-page-routing.module.ts - 13 - - FIRE FIRE @@ -2391,7 +2387,7 @@ apps/client/src/app/pages/portfolio/portfolio-page-routing.module.ts - 41 + 46 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -2619,7 +2615,7 @@ Inizia apps/client/src/app/components/header/header.component.html - 407 + 422 @@ -3227,7 +3223,7 @@ Comunità apps/client/src/app/app.component.html - 120 + 121 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -3397,6 +3393,10 @@ apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html 32 + + libs/ui/src/lib/assistant/assistant.html + 127 + Load Dividends @@ -3523,7 +3523,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 203 + 213 @@ -3539,7 +3539,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 211 + 221 @@ -3555,7 +3555,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 215 + 225 @@ -3571,7 +3571,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 242 + 252 @@ -3587,7 +3587,7 @@ Aggiorna il piano apps/client/src/app/components/header/header.component.html - 180 + 185 apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html @@ -3599,7 +3599,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 278 + 288 @@ -3619,11 +3619,11 @@ apps/client/src/app/pages/pricing/pricing-page.html - 129 + 134 apps/client/src/app/pages/pricing/pricing-page.html - 191 + 201 @@ -3635,11 +3635,11 @@ apps/client/src/app/pages/pricing/pricing-page.html - 133 + 138 apps/client/src/app/pages/pricing/pricing-page.html - 195 + 205 @@ -3651,11 +3651,11 @@ apps/client/src/app/pages/pricing/pricing-page.html - 137 + 142 apps/client/src/app/pages/pricing/pricing-page.html - 199 + 209 @@ -3675,7 +3675,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 153 + 158 @@ -3683,7 +3683,7 @@ Per i nuovi investitori che hanno appena iniziato a fare trading. apps/client/src/app/pages/pricing/pricing-page.html - 123 + 128 @@ -3691,11 +3691,11 @@ Offerta cloud Ghostfolio completamente gestita. apps/client/src/app/pages/pricing/pricing-page.html - 152 + 157 apps/client/src/app/pages/pricing/pricing-page.html - 251 + 261 @@ -3703,7 +3703,7 @@ Per gli investitori ambiziosi che hanno bisogno di un quadro completo dei propri asset finanziari. apps/client/src/app/pages/pricing/pricing-page.html - 184 + 194 @@ -3711,7 +3711,7 @@ Pagamento una tantum, senza rinnovo automatico. apps/client/src/app/pages/pricing/pricing-page.html - 288 + 298 @@ -3727,7 +3727,7 @@ È gratuito. apps/client/src/app/pages/pricing/pricing-page.html - 309 + 327 @@ -3742,8 +3742,8 @@ 84 - apps/client/src/app/pages/portfolio/fire/fire-page.html - 198 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 88 @@ -3763,7 +3763,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 207 + 217 @@ -3783,11 +3783,11 @@ apps/client/src/app/pages/pricing/pricing-page.html - 141 + 146 apps/client/src/app/pages/pricing/pricing-page.html - 219 + 229 @@ -3811,7 +3811,7 @@ Supporto via email e chat apps/client/src/app/pages/pricing/pricing-page.html - 247 + 257 @@ -3871,7 +3871,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 230 + 240 @@ -3887,7 +3887,7 @@ Rinnova il piano apps/client/src/app/components/header/header.component.html - 186 + 191 apps/client/src/app/components/user-account-membership/user-account-membership.html @@ -3895,7 +3895,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 284 + 294 @@ -4131,7 +4131,7 @@ Finanza personale apps/client/src/app/app.component.html - 56 + 57 @@ -4139,7 +4139,7 @@ Domande più frequenti (FAQ) apps/client/src/app/app.component.html - 82 + 83 apps/client/src/app/pages/about/overview/about-overview-page.html @@ -4923,7 +4923,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 306 + 324 @@ -5192,7 +5192,7 @@ snake-case apps/client/src/app/app.component.ts - 76 + 77 apps/client/src/app/core/paths.ts @@ -5225,15 +5225,15 @@ snake-case apps/client/src/app/app.component.ts - 77 + 78 apps/client/src/app/components/header/header.component.ts - 81 + 82 apps/client/src/app/components/header/header.component.ts - 86 + 87 apps/client/src/app/core/paths.ts @@ -5271,13 +5271,17 @@ apps/client/src/app/pages/blog/2023/11/hacktoberfest-2023-debriefing/hacktoberfest-2023-debriefing-page.component.ts 14 + + apps/client/src/app/pages/blog/2024/11/black-weeks-2024/black-weeks-2024-page.component.ts + 15 + apps/client/src/app/pages/faq/overview/faq-overview-page.component.ts 14 apps/client/src/app/pages/pricing/pricing-page.component.ts - 39 + 41 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -5290,27 +5294,27 @@ snake-case apps/client/src/app/app.component.ts - 63 + 64 apps/client/src/app/app.component.ts - 65 + 66 apps/client/src/app/app.component.ts - 69 + 70 apps/client/src/app/app.component.ts - 73 + 74 apps/client/src/app/components/header/header.component.ts - 80 + 81 apps/client/src/app/components/header/header.component.ts - 85 + 86 apps/client/src/app/core/paths.ts @@ -5379,7 +5383,7 @@ snake-case apps/client/src/app/app.component.ts - 74 + 75 apps/client/src/app/core/paths.ts @@ -5396,7 +5400,7 @@ snake-case apps/client/src/app/app.component.ts - 70 + 71 apps/client/src/app/core/paths.ts @@ -5413,15 +5417,15 @@ snake-case apps/client/src/app/app.component.ts - 78 + 79 apps/client/src/app/components/header/header.component.ts - 82 + 83 apps/client/src/app/components/header/header.component.ts - 87 + 88 apps/client/src/app/core/paths.ts @@ -5454,7 +5458,7 @@ snake-case apps/client/src/app/app.component.ts - 79 + 80 apps/client/src/app/components/admin-settings/admin-settings.component.ts @@ -5462,11 +5466,11 @@ apps/client/src/app/components/header/header.component.ts - 83 + 84 apps/client/src/app/components/header/header.component.ts - 88 + 89 apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.component.ts @@ -5474,7 +5478,7 @@ apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 38 + 40 apps/client/src/app/core/http-response.interceptor.ts @@ -5512,6 +5516,10 @@ apps/client/src/app/pages/blog/2023/11/black-week-2023/black-week-2023-page.component.ts 16 + + apps/client/src/app/pages/blog/2024/11/black-weeks-2024/black-weeks-2024-page.component.ts + 16 + apps/client/src/app/pages/faq/saas/saas-page.component.ts 15 @@ -5527,11 +5535,11 @@ snake-case apps/client/src/app/app.component.ts - 80 + 81 apps/client/src/app/components/header/header.component.ts - 89 + 90 apps/client/src/app/core/auth.guard.ts @@ -5555,7 +5563,7 @@ apps/client/src/app/pages/pricing/pricing-page.component.ts - 40 + 42 @@ -5564,15 +5572,15 @@ snake-case apps/client/src/app/app.component.ts - 81 + 82 apps/client/src/app/components/header/header.component.ts - 84 + 85 apps/client/src/app/components/header/header.component.ts - 90 + 91 apps/client/src/app/core/paths.ts @@ -5871,28 +5879,20 @@ 10 - - Ghostfolio X-ray uses static analysis to identify potential issues and risks in your portfolio. - Ghostfolio X-ray usa l'analisi statica per identificare potenziali problemi e rischi del tuo portafoglio. - - apps/client/src/app/pages/portfolio/fire/fire-page.html - 111 - - Currency Cluster Risks Rischio di Concentrazione Valutario - apps/client/src/app/pages/portfolio/fire/fire-page.html - 141 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 31 Account Cluster Risks Rischi di Concentrazione dei Conti - apps/client/src/app/pages/portfolio/fire/fire-page.html - 160 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 50 @@ -5984,7 +5984,7 @@ Trova possedimenti... libs/ui/src/lib/assistant/assistant.component.ts - 139 + 144 @@ -6352,7 +6352,7 @@ Da inizio settimana libs/ui/src/lib/assistant/assistant.component.ts - 212 + 225 @@ -6360,7 +6360,7 @@ Settimana corrente libs/ui/src/lib/assistant/assistant.component.ts - 212 + 225 @@ -6368,7 +6368,7 @@ Da inizio mese libs/ui/src/lib/assistant/assistant.component.ts - 216 + 229 @@ -6376,7 +6376,7 @@ Mese corrente libs/ui/src/lib/assistant/assistant.component.ts - 216 + 229 @@ -6384,7 +6384,7 @@ Da inizio anno libs/ui/src/lib/assistant/assistant.component.ts - 220 + 233 @@ -6420,7 +6420,7 @@ Reset Filtri libs/ui/src/lib/assistant/assistant.html - 157 + 185 @@ -6428,7 +6428,7 @@ anno libs/ui/src/lib/assistant/assistant.component.ts - 224 + 237 @@ -6436,7 +6436,7 @@ anni libs/ui/src/lib/assistant/assistant.component.ts - 246 + 259 @@ -6444,7 +6444,7 @@ classi degli Asset libs/ui/src/lib/assistant/assistant.html - 138 + 166 @@ -6452,7 +6452,7 @@ Applica i Filtri libs/ui/src/lib/assistant/assistant.html - 167 + 195 @@ -6597,7 +6597,7 @@ {VAR_PLURAL, plural, =1 {activity} other {activities}} - {VAR_PLURAL, plural, =1 {attività} other {attività}} + {VAR_PLURAL, plural, =1 {attività} other {attività}} apps/client/src/app/components/portfolio-summary/portfolio-summary.component.html 14 @@ -6728,7 +6728,7 @@ Tabella apps/client/src/app/components/home-holdings/home-holdings.html - 17 + 16 @@ -6736,7 +6736,7 @@ Grafico apps/client/src/app/components/home-holdings/home-holdings.html - 20 + 19 @@ -7031,8 +7031,8 @@ Inactive Inattivo - apps/client/src/app/pages/portfolio/fire/fire-page.html - 217 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 107 @@ -7096,7 +7096,7 @@ Soglia Minima apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 9 + 54 @@ -7104,7 +7104,7 @@ Soglia Massima apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 44 + 92 @@ -7112,7 +7112,7 @@ Chiudi apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 77 + 129 @@ -7128,7 +7128,7 @@ No rinnovo automatico. apps/client/src/app/components/user-account-membership/user-account-membership.html - 62 + 72 @@ -7149,7 +7149,7 @@ From the beginning - Dall'inizio + Dall'inizio apps/client/src/app/pages/public/public-page.html 60 @@ -7205,7 +7205,7 @@ can be self-hosted - può essere ospitato in proprio + può essere ospitato in proprio apps/client/src/app/pages/resources/personal-finance-tools/product-page.html 178 @@ -7213,7 +7213,7 @@ cannot be self-hosted - non può essere ospitato in proprio + non può essere ospitato in proprio apps/client/src/app/pages/resources/personal-finance-tools/product-page.html 185 @@ -7221,7 +7221,7 @@ can be self-hosted - può essere ospitato in proprio + può essere ospitato in proprio apps/client/src/app/pages/resources/personal-finance-tools/product-page.html 195 @@ -7229,7 +7229,7 @@ cannot be self-hosted - non può essere ospitato in proprio + non può essere ospitato in proprio apps/client/src/app/pages/resources/personal-finance-tools/product-page.html 202 @@ -7237,7 +7237,7 @@ can be used anonymously - può essere usato anonimamente + può essere usato anonimamente apps/client/src/app/pages/resources/personal-finance-tools/product-page.html 217 @@ -7245,7 +7245,7 @@ cannot be used anonymously - non può essere usato anonimamente + non può essere usato anonimamente apps/client/src/app/pages/resources/personal-finance-tools/product-page.html 224 @@ -7253,7 +7253,7 @@ can be used anonymously - può essere usato anonimamente + può essere usato anonimamente apps/client/src/app/pages/resources/personal-finance-tools/product-page.html 234 @@ -7261,7 +7261,7 @@ cannot be used anonymously - non può essere usato anonimamente + non può essere usato anonimamente apps/client/src/app/pages/resources/personal-finance-tools/product-page.html 241 @@ -7379,14 +7379,6 @@ 93 - - Allocation Cluster Risks - Rischi di allocazione dei Conti - - apps/client/src/app/pages/portfolio/fire/fire-page.html - 179 - - Glossary Glossario @@ -7437,6 +7429,30 @@ 21 + + Threshold range + Threshold range + + apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html + 9 + + + + Ghostfolio X-ray uses static analysis to uncover potential issues and risks in your portfolio. Adjust the rules below and set custom thresholds to align with your personal investment strategy. + Ghostfolio X-ray uses static analysis to uncover potential issues and risks in your portfolio. Adjust the rules below and set custom thresholds to align with your personal investment strategy. + + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 5 + + + + Economic Market Cluster Risks + Economic Market Cluster Risks + + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 69 + + diff --git a/apps/client/src/locales/messages.nl.xlf b/apps/client/src/locales/messages.nl.xlf index 9a12eb0f..f590449d 100644 --- a/apps/client/src/locales/messages.nl.xlf +++ b/apps/client/src/locales/messages.nl.xlf @@ -22,7 +22,7 @@ Het risico op verlies bij handelen kan aanzienlijk zijn. Het is niet aan te raden om geld te investeren dat je misschien op korte termijn nodig heeft. apps/client/src/app/app.component.html - 199 + 200 @@ -554,7 +554,7 @@ apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 83 + 135 apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html @@ -790,7 +790,7 @@ apps/client/src/app/components/header/header.component.html - 224 + 229 @@ -834,7 +834,7 @@ apps/client/src/app/components/header/header.component.html - 240 + 245 @@ -846,7 +846,7 @@ apps/client/src/app/components/header/header.component.html - 250 + 255 @@ -866,7 +866,7 @@ apps/client/src/app/components/header/header.component.html - 258 + 263 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -890,7 +890,7 @@ apps/client/src/app/components/header/header.component.html - 274 + 279 @@ -898,7 +898,7 @@ Middelen apps/client/src/app/app.component.html - 63 + 64 apps/client/src/app/components/header/header.component.html @@ -906,7 +906,7 @@ apps/client/src/app/components/header/header.component.html - 286 + 291 apps/client/src/app/pages/resources/overview/resources-overview.component.html @@ -918,19 +918,19 @@ Prijzen apps/client/src/app/app.component.html - 96 + 97 apps/client/src/app/components/header/header.component.html - 98 + 99 apps/client/src/app/components/header/header.component.html - 297 + 303 apps/client/src/app/components/header/header.component.html - 368 + 379 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -942,15 +942,15 @@ Over apps/client/src/app/app.component.html - 69 + 70 apps/client/src/app/components/header/header.component.html - 112 + 117 apps/client/src/app/components/header/header.component.html - 354 + 364 @@ -958,7 +958,7 @@ Ik apps/client/src/app/components/header/header.component.html - 206 + 211 @@ -966,7 +966,7 @@ Mijn Ghostfolio apps/client/src/app/components/header/header.component.html - 265 + 270 @@ -974,7 +974,7 @@ Over Ghostfolio apps/client/src/app/components/header/header.component.html - 306 + 316 apps/client/src/app/pages/about/overview/about-overview-page.html @@ -986,11 +986,11 @@ Functionaliteiten apps/client/src/app/app.component.html - 78 + 79 apps/client/src/app/components/header/header.component.html - 341 + 351 apps/client/src/app/pages/features/features-page.html @@ -1002,11 +1002,11 @@ Markten apps/client/src/app/app.component.html - 60 + 61 apps/client/src/app/components/header/header.component.html - 383 + 398 apps/client/src/app/components/home-market/home-market.html @@ -1038,7 +1038,7 @@ apps/client/src/app/components/header/header.component.ts - 229 + 230 @@ -1046,7 +1046,7 @@ Oeps! Onjuiste beveiligingstoken. apps/client/src/app/components/header/header.component.ts - 244 + 245 apps/client/src/app/components/user-account-settings/user-account-settings.component.ts @@ -1058,7 +1058,7 @@ Activiteiten beheren apps/client/src/app/components/home-holdings/home-holdings.html - 65 + 63 @@ -1150,7 +1150,7 @@ Aanmelden apps/client/src/app/components/header/header.component.html - 397 + 412 apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html @@ -1245,8 +1245,8 @@ 89 - apps/client/src/app/pages/portfolio/fire/fire-page.html - 122 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 12 @@ -1362,7 +1362,7 @@ libs/ui/src/lib/assistant/assistant.html - 127 + 155 @@ -1414,7 +1414,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 210 + 223 @@ -1426,7 +1426,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 220 + 233 @@ -1438,7 +1438,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 224 + 237 @@ -1450,7 +1450,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 246 + 259 @@ -1462,7 +1462,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 249 + 262 @@ -1498,7 +1498,7 @@ Privacybeleid apps/client/src/app/app.component.html - 102 + 103 apps/client/src/app/pages/about/privacy-policy/privacy-policy-page.html @@ -1510,7 +1510,7 @@ Blog apps/client/src/app/app.component.html - 72 + 73 apps/client/src/app/pages/blog/2021/07/hallo-ghostfolio/hallo-ghostfolio-page.html @@ -1592,6 +1592,10 @@ apps/client/src/app/pages/blog/2024/09/hacktoberfest-2024/hacktoberfest-2024-page.html 187 + + apps/client/src/app/pages/blog/2024/11/black-weeks-2024/black-weeks-2024-page.html + 167 + apps/client/src/app/pages/blog/blog-page.html 5 @@ -1602,7 +1606,7 @@ Changelog apps/client/src/app/app.component.html - 76 + 77 apps/client/src/app/pages/about/changelog/changelog-page.html @@ -1614,7 +1618,7 @@ Licentie apps/client/src/app/app.component.html - 87 + 88 apps/client/src/app/pages/about/license/license-page.html @@ -1646,7 +1650,7 @@ Voer je couponcode in: apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 115 + 124 @@ -1654,7 +1658,7 @@ Kon je kortingscode niet inwisselen apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 125 + 134 @@ -1662,7 +1666,7 @@ Je couponcode is ingewisseld apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 137 + 146 @@ -1670,7 +1674,7 @@ Herladen apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 138 + 147 @@ -1710,7 +1714,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 264 + 274 @@ -1718,7 +1722,7 @@ Probeer Premium apps/client/src/app/components/user-account-membership/user-account-membership.html - 41 + 51 @@ -1726,7 +1730,7 @@ Coupon inwisselen apps/client/src/app/components/user-account-membership/user-account-membership.html - 55 + 65 @@ -2145,14 +2149,6 @@ 214 - - FIRE - FIRE - - apps/client/src/app/pages/portfolio/fire/fire-page-routing.module.ts - 13 - - FIRE FIRE @@ -2390,7 +2386,7 @@ apps/client/src/app/pages/portfolio/portfolio-page-routing.module.ts - 41 + 46 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -2618,7 +2614,7 @@ Aan de slag apps/client/src/app/components/header/header.component.html - 407 + 422 @@ -3226,7 +3222,7 @@ Gemeenschap apps/client/src/app/app.component.html - 120 + 121 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -3396,6 +3392,10 @@ apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html 32 + + libs/ui/src/lib/assistant/assistant.html + 127 + Load Dividends @@ -3522,7 +3522,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 203 + 213 @@ -3538,7 +3538,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 211 + 221 @@ -3554,7 +3554,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 215 + 225 @@ -3570,7 +3570,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 242 + 252 @@ -3586,7 +3586,7 @@ Abonnement uitbreiden apps/client/src/app/components/header/header.component.html - 180 + 185 apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html @@ -3598,7 +3598,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 278 + 288 @@ -3618,11 +3618,11 @@ apps/client/src/app/pages/pricing/pricing-page.html - 129 + 134 apps/client/src/app/pages/pricing/pricing-page.html - 191 + 201 @@ -3634,11 +3634,11 @@ apps/client/src/app/pages/pricing/pricing-page.html - 133 + 138 apps/client/src/app/pages/pricing/pricing-page.html - 195 + 205 @@ -3650,11 +3650,11 @@ apps/client/src/app/pages/pricing/pricing-page.html - 137 + 142 apps/client/src/app/pages/pricing/pricing-page.html - 199 + 209 @@ -3674,7 +3674,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 153 + 158 @@ -3682,7 +3682,7 @@ Voor nieuwe beleggers die net beginnen met handelen. apps/client/src/app/pages/pricing/pricing-page.html - 123 + 128 @@ -3690,11 +3690,11 @@ Volledig beheerd Ghostfolio cloud-aanbod. apps/client/src/app/pages/pricing/pricing-page.html - 152 + 157 apps/client/src/app/pages/pricing/pricing-page.html - 251 + 261 @@ -3702,7 +3702,7 @@ Voor ambitieuze beleggers die een volledig beeld willen hebben van hun financiële assets. apps/client/src/app/pages/pricing/pricing-page.html - 184 + 194 @@ -3710,7 +3710,7 @@ Eenmalige betaling, geen automatische verlenging. apps/client/src/app/pages/pricing/pricing-page.html - 288 + 298 @@ -3726,7 +3726,7 @@ Het is gratis. apps/client/src/app/pages/pricing/pricing-page.html - 309 + 327 @@ -3741,8 +3741,8 @@ 84 - apps/client/src/app/pages/portfolio/fire/fire-page.html - 198 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 88 @@ -3762,7 +3762,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 207 + 217 @@ -3782,11 +3782,11 @@ apps/client/src/app/pages/pricing/pricing-page.html - 141 + 146 apps/client/src/app/pages/pricing/pricing-page.html - 219 + 229 @@ -3810,7 +3810,7 @@ Ondersteuning via e-mail en chat apps/client/src/app/pages/pricing/pricing-page.html - 247 + 257 @@ -3870,7 +3870,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 230 + 240 @@ -3886,7 +3886,7 @@ Abonnement Vernieuwen apps/client/src/app/components/header/header.component.html - 186 + 191 apps/client/src/app/components/user-account-membership/user-account-membership.html @@ -3894,7 +3894,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 284 + 294 @@ -4130,7 +4130,7 @@ Persoonlijke financiën apps/client/src/app/app.component.html - 56 + 57 @@ -4138,7 +4138,7 @@ Veelgestelde Vragen apps/client/src/app/app.component.html - 82 + 83 apps/client/src/app/pages/about/overview/about-overview-page.html @@ -4922,7 +4922,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 306 + 324 @@ -5191,7 +5191,7 @@ snake-case apps/client/src/app/app.component.ts - 76 + 77 apps/client/src/app/core/paths.ts @@ -5224,15 +5224,15 @@ snake-case apps/client/src/app/app.component.ts - 77 + 78 apps/client/src/app/components/header/header.component.ts - 81 + 82 apps/client/src/app/components/header/header.component.ts - 86 + 87 apps/client/src/app/core/paths.ts @@ -5270,13 +5270,17 @@ apps/client/src/app/pages/blog/2023/11/hacktoberfest-2023-debriefing/hacktoberfest-2023-debriefing-page.component.ts 14 + + apps/client/src/app/pages/blog/2024/11/black-weeks-2024/black-weeks-2024-page.component.ts + 15 + apps/client/src/app/pages/faq/overview/faq-overview-page.component.ts 14 apps/client/src/app/pages/pricing/pricing-page.component.ts - 39 + 41 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -5289,27 +5293,27 @@ snake-case apps/client/src/app/app.component.ts - 63 + 64 apps/client/src/app/app.component.ts - 65 + 66 apps/client/src/app/app.component.ts - 69 + 70 apps/client/src/app/app.component.ts - 73 + 74 apps/client/src/app/components/header/header.component.ts - 80 + 81 apps/client/src/app/components/header/header.component.ts - 85 + 86 apps/client/src/app/core/paths.ts @@ -5378,7 +5382,7 @@ snake-case apps/client/src/app/app.component.ts - 74 + 75 apps/client/src/app/core/paths.ts @@ -5395,7 +5399,7 @@ snake-case apps/client/src/app/app.component.ts - 70 + 71 apps/client/src/app/core/paths.ts @@ -5412,15 +5416,15 @@ snake-case apps/client/src/app/app.component.ts - 78 + 79 apps/client/src/app/components/header/header.component.ts - 82 + 83 apps/client/src/app/components/header/header.component.ts - 87 + 88 apps/client/src/app/core/paths.ts @@ -5453,7 +5457,7 @@ snake-case apps/client/src/app/app.component.ts - 79 + 80 apps/client/src/app/components/admin-settings/admin-settings.component.ts @@ -5461,11 +5465,11 @@ apps/client/src/app/components/header/header.component.ts - 83 + 84 apps/client/src/app/components/header/header.component.ts - 88 + 89 apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.component.ts @@ -5473,7 +5477,7 @@ apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 38 + 40 apps/client/src/app/core/http-response.interceptor.ts @@ -5511,6 +5515,10 @@ apps/client/src/app/pages/blog/2023/11/black-week-2023/black-week-2023-page.component.ts 16 + + apps/client/src/app/pages/blog/2024/11/black-weeks-2024/black-weeks-2024-page.component.ts + 16 + apps/client/src/app/pages/faq/saas/saas-page.component.ts 15 @@ -5526,11 +5534,11 @@ snake-case apps/client/src/app/app.component.ts - 80 + 81 apps/client/src/app/components/header/header.component.ts - 89 + 90 apps/client/src/app/core/auth.guard.ts @@ -5554,7 +5562,7 @@ apps/client/src/app/pages/pricing/pricing-page.component.ts - 40 + 42 @@ -5563,15 +5571,15 @@ snake-case apps/client/src/app/app.component.ts - 81 + 82 apps/client/src/app/components/header/header.component.ts - 84 + 85 apps/client/src/app/components/header/header.component.ts - 90 + 91 apps/client/src/app/core/paths.ts @@ -5870,28 +5878,20 @@ 10 - - Ghostfolio X-ray uses static analysis to identify potential issues and risks in your portfolio. - Ghostfolio X-ray uses static analysis to identify potential issues and risks in your portfolio. - - apps/client/src/app/pages/portfolio/fire/fire-page.html - 111 - - Currency Cluster Risks Currency Cluster Risks - apps/client/src/app/pages/portfolio/fire/fire-page.html - 141 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 31 Account Cluster Risks Account Cluster Risks - apps/client/src/app/pages/portfolio/fire/fire-page.html - 160 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 50 @@ -5983,7 +5983,7 @@ Find holding... libs/ui/src/lib/assistant/assistant.component.ts - 139 + 144 @@ -6351,7 +6351,7 @@ Week to date libs/ui/src/lib/assistant/assistant.component.ts - 212 + 225 @@ -6359,7 +6359,7 @@ WTD libs/ui/src/lib/assistant/assistant.component.ts - 212 + 225 @@ -6367,7 +6367,7 @@ Month to date libs/ui/src/lib/assistant/assistant.component.ts - 216 + 229 @@ -6375,7 +6375,7 @@ MTD libs/ui/src/lib/assistant/assistant.component.ts - 216 + 229 @@ -6383,7 +6383,7 @@ Year to date libs/ui/src/lib/assistant/assistant.component.ts - 220 + 233 @@ -6419,7 +6419,7 @@ Reset Filters libs/ui/src/lib/assistant/assistant.html - 157 + 185 @@ -6427,7 +6427,7 @@ year libs/ui/src/lib/assistant/assistant.component.ts - 224 + 237 @@ -6435,7 +6435,7 @@ years libs/ui/src/lib/assistant/assistant.component.ts - 246 + 259 @@ -6443,7 +6443,7 @@ Asset Classes libs/ui/src/lib/assistant/assistant.html - 138 + 166 @@ -6451,7 +6451,7 @@ Apply Filters libs/ui/src/lib/assistant/assistant.html - 167 + 195 @@ -6727,7 +6727,7 @@ Table apps/client/src/app/components/home-holdings/home-holdings.html - 17 + 16 @@ -6735,7 +6735,7 @@ Chart apps/client/src/app/components/home-holdings/home-holdings.html - 20 + 19 @@ -7030,8 +7030,8 @@ Inactive Inactive - apps/client/src/app/pages/portfolio/fire/fire-page.html - 217 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 107 @@ -7095,7 +7095,7 @@ Threshold Min apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 9 + 54 @@ -7103,7 +7103,7 @@ Threshold Max apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 44 + 92 @@ -7111,7 +7111,7 @@ Close apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 77 + 129 @@ -7127,7 +7127,7 @@ No auto-renewal. apps/client/src/app/components/user-account-membership/user-account-membership.html - 62 + 72 @@ -7378,14 +7378,6 @@ 93 - - Allocation Cluster Risks - Allocation Cluster Risks - - apps/client/src/app/pages/portfolio/fire/fire-page.html - 179 - - Glossary Glossary @@ -7436,6 +7428,30 @@ 21 + + Threshold range + Threshold range + + apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html + 9 + + + + Ghostfolio X-ray uses static analysis to uncover potential issues and risks in your portfolio. Adjust the rules below and set custom thresholds to align with your personal investment strategy. + Ghostfolio X-ray uses static analysis to uncover potential issues and risks in your portfolio. Adjust the rules below and set custom thresholds to align with your personal investment strategy. + + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 5 + + + + Economic Market Cluster Risks + Economic Market Cluster Risks + + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 69 + + diff --git a/apps/client/src/locales/messages.pl.xlf b/apps/client/src/locales/messages.pl.xlf index 3a183045..04b15671 100644 --- a/apps/client/src/locales/messages.pl.xlf +++ b/apps/client/src/locales/messages.pl.xlf @@ -7,27 +7,27 @@ snake-case apps/client/src/app/app.component.ts - 63 + 64 apps/client/src/app/app.component.ts - 65 + 66 apps/client/src/app/app.component.ts - 69 + 70 apps/client/src/app/app.component.ts - 73 + 74 apps/client/src/app/components/header/header.component.ts - 80 + 81 apps/client/src/app/components/header/header.component.ts - 85 + 86 apps/client/src/app/core/paths.ts @@ -96,7 +96,7 @@ snake-case apps/client/src/app/app.component.ts - 76 + 77 apps/client/src/app/core/paths.ts @@ -129,15 +129,15 @@ snake-case apps/client/src/app/app.component.ts - 77 + 78 apps/client/src/app/components/header/header.component.ts - 81 + 82 apps/client/src/app/components/header/header.component.ts - 86 + 87 apps/client/src/app/core/paths.ts @@ -175,13 +175,17 @@ apps/client/src/app/pages/blog/2023/11/hacktoberfest-2023-debriefing/hacktoberfest-2023-debriefing-page.component.ts 14 + + apps/client/src/app/pages/blog/2024/11/black-weeks-2024/black-weeks-2024-page.component.ts + 15 + apps/client/src/app/pages/faq/overview/faq-overview-page.component.ts 14 apps/client/src/app/pages/pricing/pricing-page.component.ts - 39 + 41 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -194,7 +198,7 @@ snake-case apps/client/src/app/app.component.ts - 70 + 71 apps/client/src/app/core/paths.ts @@ -211,15 +215,15 @@ snake-case apps/client/src/app/app.component.ts - 78 + 79 apps/client/src/app/components/header/header.component.ts - 82 + 83 apps/client/src/app/components/header/header.component.ts - 87 + 88 apps/client/src/app/core/paths.ts @@ -252,7 +256,7 @@ snake-case apps/client/src/app/app.component.ts - 79 + 80 apps/client/src/app/components/admin-settings/admin-settings.component.ts @@ -260,11 +264,11 @@ apps/client/src/app/components/header/header.component.ts - 83 + 84 apps/client/src/app/components/header/header.component.ts - 88 + 89 apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.component.ts @@ -272,7 +276,7 @@ apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 38 + 40 apps/client/src/app/core/http-response.interceptor.ts @@ -310,6 +314,10 @@ apps/client/src/app/pages/blog/2023/11/black-week-2023/black-week-2023-page.component.ts 16 + + apps/client/src/app/pages/blog/2024/11/black-weeks-2024/black-weeks-2024-page.component.ts + 16 + apps/client/src/app/pages/faq/saas/saas-page.component.ts 15 @@ -325,7 +333,7 @@ snake-case apps/client/src/app/app.component.ts - 74 + 75 apps/client/src/app/core/paths.ts @@ -342,11 +350,11 @@ snake-case apps/client/src/app/app.component.ts - 80 + 81 apps/client/src/app/components/header/header.component.ts - 89 + 90 apps/client/src/app/core/auth.guard.ts @@ -370,7 +378,7 @@ apps/client/src/app/pages/pricing/pricing-page.component.ts - 40 + 42 @@ -379,15 +387,15 @@ snake-case apps/client/src/app/app.component.ts - 81 + 82 apps/client/src/app/components/header/header.component.ts - 84 + 85 apps/client/src/app/components/header/header.component.ts - 90 + 91 apps/client/src/app/core/paths.ts @@ -467,7 +475,7 @@ Finanse Osobiste apps/client/src/app/app.component.html - 56 + 57 @@ -475,11 +483,11 @@ Rynki apps/client/src/app/app.component.html - 60 + 61 apps/client/src/app/components/header/header.component.html - 383 + 398 apps/client/src/app/components/home-market/home-market.html @@ -495,7 +503,7 @@ Zasoby apps/client/src/app/app.component.html - 63 + 64 apps/client/src/app/components/header/header.component.html @@ -503,7 +511,7 @@ apps/client/src/app/components/header/header.component.html - 286 + 291 apps/client/src/app/pages/resources/overview/resources-overview.component.html @@ -515,15 +523,15 @@ O programie apps/client/src/app/app.component.html - 69 + 70 apps/client/src/app/components/header/header.component.html - 112 + 117 apps/client/src/app/components/header/header.component.html - 354 + 364 @@ -531,7 +539,7 @@ Blog apps/client/src/app/app.component.html - 72 + 73 apps/client/src/app/pages/blog/2021/07/hallo-ghostfolio/hallo-ghostfolio-page.html @@ -613,6 +621,10 @@ apps/client/src/app/pages/blog/2024/09/hacktoberfest-2024/hacktoberfest-2024-page.html 187 + + apps/client/src/app/pages/blog/2024/11/black-weeks-2024/black-weeks-2024-page.html + 167 + apps/client/src/app/pages/blog/blog-page.html 5 @@ -623,7 +635,7 @@ Dziennik Zmian apps/client/src/app/app.component.html - 76 + 77 apps/client/src/app/pages/about/changelog/changelog-page.html @@ -635,11 +647,11 @@ Funkcje apps/client/src/app/app.component.html - 78 + 79 apps/client/src/app/components/header/header.component.html - 341 + 351 apps/client/src/app/pages/features/features-page.html @@ -651,7 +663,7 @@ Często Zadawane Pytania (FAQ) apps/client/src/app/app.component.html - 82 + 83 apps/client/src/app/pages/about/overview/about-overview-page.html @@ -663,7 +675,7 @@ Licencja apps/client/src/app/app.component.html - 87 + 88 apps/client/src/app/pages/about/license/license-page.html @@ -675,19 +687,19 @@ Cennik apps/client/src/app/app.component.html - 96 + 97 apps/client/src/app/components/header/header.component.html - 98 + 99 apps/client/src/app/components/header/header.component.html - 297 + 303 apps/client/src/app/components/header/header.component.html - 368 + 379 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -699,7 +711,7 @@ Polityka Prywatności apps/client/src/app/app.component.html - 102 + 103 apps/client/src/app/pages/about/privacy-policy/privacy-policy-page.html @@ -711,7 +723,7 @@ Społeczność apps/client/src/app/app.component.html - 120 + 121 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -759,7 +771,7 @@ Ryzyko strat na rynku może być znaczne. Nie jest zalecane inwestowanie pieniędzy, które mogą być potrzebne w krótkim okresie. apps/client/src/app/app.component.html - 199 + 200 @@ -1387,7 +1399,7 @@ apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 83 + 135 apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html @@ -1963,7 +1975,7 @@ apps/client/src/app/components/header/header.component.html - 258 + 263 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -2031,7 +2043,7 @@ libs/ui/src/lib/assistant/assistant.html - 127 + 155 @@ -2159,7 +2171,7 @@ apps/client/src/app/pages/portfolio/portfolio-page-routing.module.ts - 41 + 46 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -2191,7 +2203,7 @@ apps/client/src/app/components/header/header.component.html - 240 + 245 @@ -2203,7 +2215,7 @@ apps/client/src/app/components/header/header.component.html - 250 + 255 @@ -2215,7 +2227,7 @@ apps/client/src/app/components/header/header.component.html - 274 + 279 @@ -2223,7 +2235,7 @@ Ja apps/client/src/app/components/header/header.component.html - 206 + 211 @@ -2235,7 +2247,7 @@ apps/client/src/app/components/header/header.component.html - 224 + 229 @@ -2243,7 +2255,7 @@ Moje Ghostfolio apps/client/src/app/components/header/header.component.html - 265 + 270 @@ -2251,7 +2263,7 @@ O Ghostfolio apps/client/src/app/components/header/header.component.html - 306 + 316 apps/client/src/app/pages/about/overview/about-overview-page.html @@ -2263,7 +2275,7 @@ Zaloguj się apps/client/src/app/components/header/header.component.html - 397 + 412 apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html @@ -2275,7 +2287,7 @@ Rozpocznij apps/client/src/app/components/header/header.component.html - 407 + 422 @@ -2287,7 +2299,7 @@ apps/client/src/app/components/header/header.component.ts - 229 + 230 @@ -2295,7 +2307,7 @@ Ups! Nieprawidłowy token bezpieczeństwa. apps/client/src/app/components/header/header.component.ts - 244 + 245 apps/client/src/app/components/user-account-settings/user-account-settings.component.ts @@ -2307,7 +2319,7 @@ Zarządzaj Aktywnościami apps/client/src/app/components/home-holdings/home-holdings.html - 65 + 63 @@ -2574,8 +2586,8 @@ 84 - apps/client/src/app/pages/portfolio/fire/fire-page.html - 198 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 88 @@ -2622,8 +2634,8 @@ 89 - apps/client/src/app/pages/portfolio/fire/fire-page.html - 122 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 12 @@ -2811,7 +2823,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 203 + 213 @@ -2831,7 +2843,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 207 + 217 @@ -2847,7 +2859,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 211 + 221 @@ -2863,7 +2875,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 215 + 225 @@ -2875,7 +2887,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 230 + 240 @@ -2891,7 +2903,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 242 + 252 @@ -2915,7 +2927,7 @@ Ulepsz Plan apps/client/src/app/components/header/header.component.html - 180 + 185 apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html @@ -2927,7 +2939,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 278 + 288 @@ -2939,7 +2951,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 210 + 223 @@ -2951,7 +2963,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 220 + 233 @@ -2963,7 +2975,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 224 + 237 @@ -2975,7 +2987,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 246 + 259 @@ -2987,7 +2999,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 249 + 262 @@ -3019,7 +3031,7 @@ Wpisz kod kuponu: apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 115 + 124 @@ -3027,7 +3039,7 @@ Nie udało się zrealizować kodu kuponu apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 125 + 134 @@ -3035,7 +3047,7 @@ Kupon został zrealizowany apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 137 + 146 @@ -3043,7 +3055,7 @@ Odśwież apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 138 + 147 @@ -3055,7 +3067,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 264 + 274 @@ -3063,7 +3075,7 @@ Wypróbuj Premium apps/client/src/app/components/user-account-membership/user-account-membership.html - 41 + 51 @@ -3071,7 +3083,7 @@ Wykorzystaj kupon apps/client/src/app/components/user-account-membership/user-account-membership.html - 55 + 65 @@ -3803,7 +3815,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 306 + 324 @@ -4381,6 +4393,10 @@ apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html 32 + + libs/ui/src/lib/assistant/assistant.html + 127 + Load Dividends @@ -4722,14 +4738,6 @@ 351 - - FIRE - FIRE - - apps/client/src/app/pages/portfolio/fire/fire-page-routing.module.ts - 13 - - FIRE FIRE @@ -4754,28 +4762,20 @@ 40 - - Ghostfolio X-ray uses static analysis to identify potential issues and risks in your portfolio. - Ghostfolio X-ray wykorzystuje analizę statyczną do identyfikacji potencjalnych problemów i zagrożeń w Twoim portfelu. - - apps/client/src/app/pages/portfolio/fire/fire-page.html - 111 - - Currency Cluster Risks Currency Cluster Risks - apps/client/src/app/pages/portfolio/fire/fire-page.html - 141 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 31 Account Cluster Risks Account Cluster Risks - apps/client/src/app/pages/portfolio/fire/fire-page.html - 160 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 50 @@ -4847,11 +4847,11 @@ apps/client/src/app/pages/pricing/pricing-page.html - 129 + 134 apps/client/src/app/pages/pricing/pricing-page.html - 191 + 201 @@ -4863,11 +4863,11 @@ apps/client/src/app/pages/pricing/pricing-page.html - 133 + 138 apps/client/src/app/pages/pricing/pricing-page.html - 195 + 205 @@ -4879,11 +4879,11 @@ apps/client/src/app/pages/pricing/pricing-page.html - 137 + 142 apps/client/src/app/pages/pricing/pricing-page.html - 199 + 209 @@ -4895,11 +4895,11 @@ apps/client/src/app/pages/pricing/pricing-page.html - 141 + 146 apps/client/src/app/pages/pricing/pricing-page.html - 219 + 229 @@ -4927,7 +4927,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 153 + 158 @@ -4935,7 +4935,7 @@ Dla początkujących inwestorów, którzy dopiero zaczynają swoją przygodę z tradingiem. apps/client/src/app/pages/pricing/pricing-page.html - 123 + 128 @@ -4943,11 +4943,11 @@ W pełni zarządzana oferta Ghostfolio w chmurze. apps/client/src/app/pages/pricing/pricing-page.html - 152 + 157 apps/client/src/app/pages/pricing/pricing-page.html - 251 + 261 @@ -4955,7 +4955,7 @@ Dla ambitnych inwestorów, którzy potrzebują pełnego obrazu swoich aktywów finansowych. apps/client/src/app/pages/pricing/pricing-page.html - 184 + 194 @@ -4963,7 +4963,7 @@ Wsparcie przez E-mail i Czat apps/client/src/app/pages/pricing/pricing-page.html - 247 + 257 @@ -4971,7 +4971,7 @@ Odnów Plan apps/client/src/app/components/header/header.component.html - 186 + 191 apps/client/src/app/components/user-account-membership/user-account-membership.html @@ -4979,7 +4979,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 284 + 294 @@ -4987,7 +4987,7 @@ Płatność jednorazowa, bez automatycznego odnawiania. apps/client/src/app/pages/pricing/pricing-page.html - 288 + 298 @@ -5003,7 +5003,7 @@ It’s free. apps/client/src/app/pages/pricing/pricing-page.html - 309 + 327 @@ -5551,7 +5551,7 @@ Znajdź portfel akcji... libs/ui/src/lib/assistant/assistant.component.ts - 139 + 144 @@ -6351,7 +6351,7 @@ Week to date libs/ui/src/lib/assistant/assistant.component.ts - 212 + 225 @@ -6359,7 +6359,7 @@ WTD libs/ui/src/lib/assistant/assistant.component.ts - 212 + 225 @@ -6367,7 +6367,7 @@ Month to date libs/ui/src/lib/assistant/assistant.component.ts - 216 + 229 @@ -6375,7 +6375,7 @@ MTD libs/ui/src/lib/assistant/assistant.component.ts - 216 + 229 @@ -6383,7 +6383,7 @@ Year to date libs/ui/src/lib/assistant/assistant.component.ts - 220 + 233 @@ -6419,7 +6419,7 @@ Resetuj Filtry libs/ui/src/lib/assistant/assistant.html - 157 + 185 @@ -6427,7 +6427,7 @@ year libs/ui/src/lib/assistant/assistant.component.ts - 224 + 237 @@ -6435,7 +6435,7 @@ years libs/ui/src/lib/assistant/assistant.component.ts - 246 + 259 @@ -6443,7 +6443,7 @@ Klasy Aktywów libs/ui/src/lib/assistant/assistant.html - 138 + 166 @@ -6451,7 +6451,7 @@ Zastosuj Filtry libs/ui/src/lib/assistant/assistant.html - 167 + 195 @@ -6727,7 +6727,7 @@ Tabela apps/client/src/app/components/home-holdings/home-holdings.html - 17 + 16 @@ -6735,7 +6735,7 @@ Wykres apps/client/src/app/components/home-holdings/home-holdings.html - 20 + 19 @@ -7030,8 +7030,8 @@ Inactive Nieaktywny - apps/client/src/app/pages/portfolio/fire/fire-page.html - 217 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 107 @@ -7095,7 +7095,7 @@ Threshold Min apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 9 + 54 @@ -7103,7 +7103,7 @@ Threshold Max apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 44 + 92 @@ -7111,7 +7111,7 @@ Close apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 77 + 129 @@ -7127,7 +7127,7 @@ No auto-renewal. apps/client/src/app/components/user-account-membership/user-account-membership.html - 62 + 72 @@ -7378,14 +7378,6 @@ 93 - - Allocation Cluster Risks - Allocation Cluster Risks - - apps/client/src/app/pages/portfolio/fire/fire-page.html - 179 - - Glossary Glossary @@ -7436,6 +7428,30 @@ 21 + + Threshold range + Threshold range + + apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html + 9 + + + + Ghostfolio X-ray uses static analysis to uncover potential issues and risks in your portfolio. Adjust the rules below and set custom thresholds to align with your personal investment strategy. + Ghostfolio X-ray uses static analysis to uncover potential issues and risks in your portfolio. Adjust the rules below and set custom thresholds to align with your personal investment strategy. + + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 5 + + + + Economic Market Cluster Risks + Economic Market Cluster Risks + + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 69 + + diff --git a/apps/client/src/locales/messages.pt.xlf b/apps/client/src/locales/messages.pt.xlf index 39404bbf..bbaa17cd 100644 --- a/apps/client/src/locales/messages.pt.xlf +++ b/apps/client/src/locales/messages.pt.xlf @@ -6,7 +6,7 @@ O risco de perda em investimentos pode ser substancial. Não é aconselhável investir dinheiro que possa vir a precisar a curto prazo. apps/client/src/app/app.component.html - 199 + 200 @@ -614,7 +614,7 @@ apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 83 + 135 apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html @@ -922,7 +922,7 @@ apps/client/src/app/components/header/header.component.html - 224 + 229 @@ -950,7 +950,7 @@ apps/client/src/app/components/header/header.component.html - 258 + 263 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1010,7 +1010,7 @@ apps/client/src/app/pages/portfolio/portfolio-page-routing.module.ts - 41 + 46 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -1042,7 +1042,7 @@ apps/client/src/app/components/header/header.component.html - 240 + 245 @@ -1054,7 +1054,7 @@ apps/client/src/app/components/header/header.component.html - 250 + 255 @@ -1066,7 +1066,7 @@ apps/client/src/app/components/header/header.component.html - 274 + 279 @@ -1074,7 +1074,7 @@ Recursos apps/client/src/app/app.component.html - 63 + 64 apps/client/src/app/components/header/header.component.html @@ -1082,7 +1082,7 @@ apps/client/src/app/components/header/header.component.html - 286 + 291 apps/client/src/app/pages/resources/overview/resources-overview.component.html @@ -1094,19 +1094,19 @@ Preços apps/client/src/app/app.component.html - 96 + 97 apps/client/src/app/components/header/header.component.html - 98 + 99 apps/client/src/app/components/header/header.component.html - 297 + 303 apps/client/src/app/components/header/header.component.html - 368 + 379 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -1118,15 +1118,15 @@ Sobre apps/client/src/app/app.component.html - 69 + 70 apps/client/src/app/components/header/header.component.html - 112 + 117 apps/client/src/app/components/header/header.component.html - 354 + 364 @@ -1134,7 +1134,7 @@ Eu apps/client/src/app/components/header/header.component.html - 206 + 211 @@ -1142,7 +1142,7 @@ O meu Ghostfolio apps/client/src/app/components/header/header.component.html - 265 + 270 @@ -1150,7 +1150,7 @@ Sobre o Ghostfolio apps/client/src/app/components/header/header.component.html - 306 + 316 apps/client/src/app/pages/about/overview/about-overview-page.html @@ -1162,11 +1162,11 @@ Funcionalidades apps/client/src/app/app.component.html - 78 + 79 apps/client/src/app/components/header/header.component.html - 341 + 351 apps/client/src/app/pages/features/features-page.html @@ -1178,11 +1178,11 @@ Mercados apps/client/src/app/app.component.html - 60 + 61 apps/client/src/app/components/header/header.component.html - 383 + 398 apps/client/src/app/components/home-market/home-market.html @@ -1198,7 +1198,7 @@ Iniciar sessão apps/client/src/app/components/header/header.component.html - 397 + 412 apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html @@ -1210,7 +1210,7 @@ Começar apps/client/src/app/components/header/header.component.html - 407 + 422 @@ -1222,7 +1222,7 @@ apps/client/src/app/components/header/header.component.ts - 229 + 230 @@ -1230,7 +1230,7 @@ Oops! Token de Segurança Incorreto. apps/client/src/app/components/header/header.component.ts - 244 + 245 apps/client/src/app/components/user-account-settings/user-account-settings.component.ts @@ -1242,7 +1242,7 @@ Gerir Atividades apps/client/src/app/components/home-holdings/home-holdings.html - 65 + 63 @@ -1465,8 +1465,8 @@ 89 - apps/client/src/app/pages/portfolio/fire/fire-page.html - 122 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 12 @@ -1666,7 +1666,7 @@ libs/ui/src/lib/assistant/assistant.html - 127 + 155 @@ -1706,7 +1706,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 210 + 223 @@ -1718,7 +1718,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 220 + 233 @@ -1730,7 +1730,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 224 + 237 @@ -1742,7 +1742,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 246 + 259 @@ -1754,7 +1754,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 249 + 262 @@ -1826,7 +1826,7 @@ Changelog apps/client/src/app/app.component.html - 76 + 77 apps/client/src/app/pages/about/changelog/changelog-page.html @@ -1838,7 +1838,7 @@ Licença apps/client/src/app/app.component.html - 87 + 88 apps/client/src/app/pages/about/license/license-page.html @@ -1862,7 +1862,7 @@ Política de Privacidade apps/client/src/app/app.component.html - 102 + 103 apps/client/src/app/pages/about/privacy-policy/privacy-policy-page.html @@ -1890,7 +1890,7 @@ Por favor, insira o seu código de cupão: apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 115 + 124 @@ -1898,7 +1898,7 @@ Não foi possível resgatar o código de cupão apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 125 + 134 @@ -1906,7 +1906,7 @@ Código de cupão foi resgatado apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 137 + 146 @@ -1914,7 +1914,7 @@ Atualizar apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 138 + 147 @@ -1954,7 +1954,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 264 + 274 @@ -1962,7 +1962,7 @@ Experimentar Premium apps/client/src/app/components/user-account-membership/user-account-membership.html - 41 + 51 @@ -1970,7 +1970,7 @@ Resgatar Cupão apps/client/src/app/components/user-account-membership/user-account-membership.html - 55 + 65 @@ -2182,7 +2182,7 @@ Blog apps/client/src/app/app.component.html - 72 + 73 apps/client/src/app/pages/blog/2021/07/hallo-ghostfolio/hallo-ghostfolio-page.html @@ -2264,6 +2264,10 @@ apps/client/src/app/pages/blog/2024/09/hacktoberfest-2024/hacktoberfest-2024-page.html 187 + + apps/client/src/app/pages/blog/2024/11/black-weeks-2024/black-weeks-2024-page.html + 167 + apps/client/src/app/pages/blog/blog-page.html 5 @@ -2673,14 +2677,6 @@ 294 - - FIRE - FIRE - - apps/client/src/app/pages/portfolio/fire/fire-page-routing.module.ts - 13 - - FIRE FIRE @@ -3270,7 +3266,7 @@ Comunidade apps/client/src/app/app.component.html - 120 + 121 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -3396,6 +3392,10 @@ apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html 32 + + libs/ui/src/lib/assistant/assistant.html + 127 + Load Dividends @@ -3522,7 +3522,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 203 + 213 @@ -3538,7 +3538,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 211 + 221 @@ -3554,7 +3554,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 215 + 225 @@ -3570,7 +3570,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 242 + 252 @@ -3586,7 +3586,7 @@ Atualizar Plano apps/client/src/app/components/header/header.component.html - 180 + 185 apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html @@ -3598,7 +3598,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 278 + 288 @@ -3618,11 +3618,11 @@ apps/client/src/app/pages/pricing/pricing-page.html - 129 + 134 apps/client/src/app/pages/pricing/pricing-page.html - 191 + 201 @@ -3634,11 +3634,11 @@ apps/client/src/app/pages/pricing/pricing-page.html - 133 + 138 apps/client/src/app/pages/pricing/pricing-page.html - 195 + 205 @@ -3650,11 +3650,11 @@ apps/client/src/app/pages/pricing/pricing-page.html - 137 + 142 apps/client/src/app/pages/pricing/pricing-page.html - 199 + 209 @@ -3674,7 +3674,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 153 + 158 @@ -3682,7 +3682,7 @@ Para novos investidores que estão a começar a investir agora. apps/client/src/app/pages/pricing/pricing-page.html - 123 + 128 @@ -3690,11 +3690,11 @@ Ghostfolio hospedado na nuvem, totalmente gerido. apps/client/src/app/pages/pricing/pricing-page.html - 152 + 157 apps/client/src/app/pages/pricing/pricing-page.html - 251 + 261 @@ -3702,7 +3702,7 @@ Para investidores ambiciosos que precisam de ter uma visão completa de seus ativos financeiros. apps/client/src/app/pages/pricing/pricing-page.html - 184 + 194 @@ -3710,7 +3710,7 @@ Pagamento único, sem renovação automática. apps/client/src/app/pages/pricing/pricing-page.html - 288 + 298 @@ -3726,7 +3726,7 @@ É gratuito. apps/client/src/app/pages/pricing/pricing-page.html - 309 + 327 @@ -3741,8 +3741,8 @@ 84 - apps/client/src/app/pages/portfolio/fire/fire-page.html - 198 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 88 @@ -3762,7 +3762,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 207 + 217 @@ -3782,11 +3782,11 @@ apps/client/src/app/pages/pricing/pricing-page.html - 141 + 146 apps/client/src/app/pages/pricing/pricing-page.html - 219 + 229 @@ -3810,7 +3810,7 @@ Suporte por Email e Chat apps/client/src/app/pages/pricing/pricing-page.html - 247 + 257 @@ -3870,7 +3870,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 230 + 240 @@ -3886,7 +3886,7 @@ Renovar Plano apps/client/src/app/components/header/header.component.html - 186 + 191 apps/client/src/app/components/user-account-membership/user-account-membership.html @@ -3894,7 +3894,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 284 + 294 @@ -4130,7 +4130,7 @@ Finanças pessoais apps/client/src/app/app.component.html - 56 + 57 @@ -4138,7 +4138,7 @@ Perguntas Frequentes (FAQ) apps/client/src/app/app.component.html - 82 + 83 apps/client/src/app/pages/about/overview/about-overview-page.html @@ -4922,7 +4922,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 306 + 324 @@ -5191,7 +5191,7 @@ snake-case apps/client/src/app/app.component.ts - 76 + 77 apps/client/src/app/core/paths.ts @@ -5224,15 +5224,15 @@ snake-case apps/client/src/app/app.component.ts - 77 + 78 apps/client/src/app/components/header/header.component.ts - 81 + 82 apps/client/src/app/components/header/header.component.ts - 86 + 87 apps/client/src/app/core/paths.ts @@ -5270,13 +5270,17 @@ apps/client/src/app/pages/blog/2023/11/hacktoberfest-2023-debriefing/hacktoberfest-2023-debriefing-page.component.ts 14 + + apps/client/src/app/pages/blog/2024/11/black-weeks-2024/black-weeks-2024-page.component.ts + 15 + apps/client/src/app/pages/faq/overview/faq-overview-page.component.ts 14 apps/client/src/app/pages/pricing/pricing-page.component.ts - 39 + 41 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -5289,27 +5293,27 @@ snake-case apps/client/src/app/app.component.ts - 63 + 64 apps/client/src/app/app.component.ts - 65 + 66 apps/client/src/app/app.component.ts - 69 + 70 apps/client/src/app/app.component.ts - 73 + 74 apps/client/src/app/components/header/header.component.ts - 80 + 81 apps/client/src/app/components/header/header.component.ts - 85 + 86 apps/client/src/app/core/paths.ts @@ -5378,7 +5382,7 @@ snake-case apps/client/src/app/app.component.ts - 74 + 75 apps/client/src/app/core/paths.ts @@ -5395,7 +5399,7 @@ snake-case apps/client/src/app/app.component.ts - 70 + 71 apps/client/src/app/core/paths.ts @@ -5412,15 +5416,15 @@ snake-case apps/client/src/app/app.component.ts - 78 + 79 apps/client/src/app/components/header/header.component.ts - 82 + 83 apps/client/src/app/components/header/header.component.ts - 87 + 88 apps/client/src/app/core/paths.ts @@ -5453,7 +5457,7 @@ snake-case apps/client/src/app/app.component.ts - 79 + 80 apps/client/src/app/components/admin-settings/admin-settings.component.ts @@ -5461,11 +5465,11 @@ apps/client/src/app/components/header/header.component.ts - 83 + 84 apps/client/src/app/components/header/header.component.ts - 88 + 89 apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.component.ts @@ -5473,7 +5477,7 @@ apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 38 + 40 apps/client/src/app/core/http-response.interceptor.ts @@ -5511,6 +5515,10 @@ apps/client/src/app/pages/blog/2023/11/black-week-2023/black-week-2023-page.component.ts 16 + + apps/client/src/app/pages/blog/2024/11/black-weeks-2024/black-weeks-2024-page.component.ts + 16 + apps/client/src/app/pages/faq/saas/saas-page.component.ts 15 @@ -5526,11 +5534,11 @@ snake-case apps/client/src/app/app.component.ts - 80 + 81 apps/client/src/app/components/header/header.component.ts - 89 + 90 apps/client/src/app/core/auth.guard.ts @@ -5554,7 +5562,7 @@ apps/client/src/app/pages/pricing/pricing-page.component.ts - 40 + 42 @@ -5563,15 +5571,15 @@ snake-case apps/client/src/app/app.component.ts - 81 + 82 apps/client/src/app/components/header/header.component.ts - 84 + 85 apps/client/src/app/components/header/header.component.ts - 90 + 91 apps/client/src/app/core/paths.ts @@ -5870,28 +5878,20 @@ 10 - - Ghostfolio X-ray uses static analysis to identify potential issues and risks in your portfolio. - Ghostfolio X-ray uses static analysis to identify potential issues and risks in your portfolio. - - apps/client/src/app/pages/portfolio/fire/fire-page.html - 111 - - Currency Cluster Risks Currency Cluster Risks - apps/client/src/app/pages/portfolio/fire/fire-page.html - 141 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 31 Account Cluster Risks Account Cluster Risks - apps/client/src/app/pages/portfolio/fire/fire-page.html - 160 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 50 @@ -5983,7 +5983,7 @@ Find holding... libs/ui/src/lib/assistant/assistant.component.ts - 139 + 144 @@ -6351,7 +6351,7 @@ Week to date libs/ui/src/lib/assistant/assistant.component.ts - 212 + 225 @@ -6359,7 +6359,7 @@ WTD libs/ui/src/lib/assistant/assistant.component.ts - 212 + 225 @@ -6367,7 +6367,7 @@ Month to date libs/ui/src/lib/assistant/assistant.component.ts - 216 + 229 @@ -6375,7 +6375,7 @@ MTD libs/ui/src/lib/assistant/assistant.component.ts - 216 + 229 @@ -6383,7 +6383,7 @@ Year to date libs/ui/src/lib/assistant/assistant.component.ts - 220 + 233 @@ -6419,7 +6419,7 @@ Reset Filters libs/ui/src/lib/assistant/assistant.html - 157 + 185 @@ -6427,7 +6427,7 @@ year libs/ui/src/lib/assistant/assistant.component.ts - 224 + 237 @@ -6435,7 +6435,7 @@ years libs/ui/src/lib/assistant/assistant.component.ts - 246 + 259 @@ -6443,7 +6443,7 @@ Asset Classes libs/ui/src/lib/assistant/assistant.html - 138 + 166 @@ -6451,7 +6451,7 @@ Apply Filters libs/ui/src/lib/assistant/assistant.html - 167 + 195 @@ -6727,7 +6727,7 @@ Table apps/client/src/app/components/home-holdings/home-holdings.html - 17 + 16 @@ -6735,7 +6735,7 @@ Chart apps/client/src/app/components/home-holdings/home-holdings.html - 20 + 19 @@ -7030,8 +7030,8 @@ Inactive Inactive - apps/client/src/app/pages/portfolio/fire/fire-page.html - 217 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 107 @@ -7095,7 +7095,7 @@ Threshold Min apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 9 + 54 @@ -7103,7 +7103,7 @@ Threshold Max apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 44 + 92 @@ -7111,7 +7111,7 @@ Close apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 77 + 129 @@ -7127,7 +7127,7 @@ No auto-renewal. apps/client/src/app/components/user-account-membership/user-account-membership.html - 62 + 72 @@ -7378,14 +7378,6 @@ 93 - - Allocation Cluster Risks - Allocation Cluster Risks - - apps/client/src/app/pages/portfolio/fire/fire-page.html - 179 - - Glossary Glossary @@ -7436,6 +7428,30 @@ 21 + + Threshold range + Threshold range + + apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html + 9 + + + + Ghostfolio X-ray uses static analysis to uncover potential issues and risks in your portfolio. Adjust the rules below and set custom thresholds to align with your personal investment strategy. + Ghostfolio X-ray uses static analysis to uncover potential issues and risks in your portfolio. Adjust the rules below and set custom thresholds to align with your personal investment strategy. + + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 5 + + + + Economic Market Cluster Risks + Economic Market Cluster Risks + + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 69 + + diff --git a/apps/client/src/locales/messages.tr.xlf b/apps/client/src/locales/messages.tr.xlf index 7d5d657c..3243ae46 100644 --- a/apps/client/src/locales/messages.tr.xlf +++ b/apps/client/src/locales/messages.tr.xlf @@ -7,27 +7,27 @@ snake-case apps/client/src/app/app.component.ts - 63 + 64 apps/client/src/app/app.component.ts - 65 + 66 apps/client/src/app/app.component.ts - 69 + 70 apps/client/src/app/app.component.ts - 73 + 74 apps/client/src/app/components/header/header.component.ts - 80 + 81 apps/client/src/app/components/header/header.component.ts - 85 + 86 apps/client/src/app/core/paths.ts @@ -96,7 +96,7 @@ snake-case apps/client/src/app/app.component.ts - 76 + 77 apps/client/src/app/core/paths.ts @@ -129,15 +129,15 @@ snake-case apps/client/src/app/app.component.ts - 77 + 78 apps/client/src/app/components/header/header.component.ts - 81 + 82 apps/client/src/app/components/header/header.component.ts - 86 + 87 apps/client/src/app/core/paths.ts @@ -175,13 +175,17 @@ apps/client/src/app/pages/blog/2023/11/hacktoberfest-2023-debriefing/hacktoberfest-2023-debriefing-page.component.ts 14 + + apps/client/src/app/pages/blog/2024/11/black-weeks-2024/black-weeks-2024-page.component.ts + 15 + apps/client/src/app/pages/faq/overview/faq-overview-page.component.ts 14 apps/client/src/app/pages/pricing/pricing-page.component.ts - 39 + 41 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -194,7 +198,7 @@ snake-case apps/client/src/app/app.component.ts - 70 + 71 apps/client/src/app/core/paths.ts @@ -211,15 +215,15 @@ snake-case apps/client/src/app/app.component.ts - 78 + 79 apps/client/src/app/components/header/header.component.ts - 82 + 83 apps/client/src/app/components/header/header.component.ts - 87 + 88 apps/client/src/app/core/paths.ts @@ -252,7 +256,7 @@ snake-case apps/client/src/app/app.component.ts - 79 + 80 apps/client/src/app/components/admin-settings/admin-settings.component.ts @@ -260,11 +264,11 @@ apps/client/src/app/components/header/header.component.ts - 83 + 84 apps/client/src/app/components/header/header.component.ts - 88 + 89 apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.component.ts @@ -272,7 +276,7 @@ apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 38 + 40 apps/client/src/app/core/http-response.interceptor.ts @@ -310,6 +314,10 @@ apps/client/src/app/pages/blog/2023/11/black-week-2023/black-week-2023-page.component.ts 16 + + apps/client/src/app/pages/blog/2024/11/black-weeks-2024/black-weeks-2024-page.component.ts + 16 + apps/client/src/app/pages/faq/saas/saas-page.component.ts 15 @@ -325,7 +333,7 @@ snake-case apps/client/src/app/app.component.ts - 74 + 75 apps/client/src/app/core/paths.ts @@ -342,11 +350,11 @@ snake-case apps/client/src/app/app.component.ts - 80 + 81 apps/client/src/app/components/header/header.component.ts - 89 + 90 apps/client/src/app/core/auth.guard.ts @@ -370,7 +378,7 @@ apps/client/src/app/pages/pricing/pricing-page.component.ts - 40 + 42 @@ -379,15 +387,15 @@ snake-case apps/client/src/app/app.component.ts - 81 + 82 apps/client/src/app/components/header/header.component.ts - 84 + 85 apps/client/src/app/components/header/header.component.ts - 90 + 91 apps/client/src/app/core/paths.ts @@ -443,7 +451,7 @@ Kişisel Finans apps/client/src/app/app.component.html - 56 + 57 @@ -451,11 +459,11 @@ Piyasalar apps/client/src/app/app.component.html - 60 + 61 apps/client/src/app/components/header/header.component.html - 383 + 398 apps/client/src/app/components/home-market/home-market.html @@ -471,7 +479,7 @@ Piyasalar apps/client/src/app/app.component.html - 63 + 64 apps/client/src/app/components/header/header.component.html @@ -479,7 +487,7 @@ apps/client/src/app/components/header/header.component.html - 286 + 291 apps/client/src/app/pages/resources/overview/resources-overview.component.html @@ -491,15 +499,15 @@ Hakkında apps/client/src/app/app.component.html - 69 + 70 apps/client/src/app/components/header/header.component.html - 112 + 117 apps/client/src/app/components/header/header.component.html - 354 + 364 @@ -507,7 +515,7 @@ Blog apps/client/src/app/app.component.html - 72 + 73 apps/client/src/app/pages/blog/2021/07/hallo-ghostfolio/hallo-ghostfolio-page.html @@ -589,6 +597,10 @@ apps/client/src/app/pages/blog/2024/09/hacktoberfest-2024/hacktoberfest-2024-page.html 187 + + apps/client/src/app/pages/blog/2024/11/black-weeks-2024/black-weeks-2024-page.html + 167 + apps/client/src/app/pages/blog/blog-page.html 5 @@ -599,7 +611,7 @@ Değişiklik Günlüğü apps/client/src/app/app.component.html - 76 + 77 apps/client/src/app/pages/about/changelog/changelog-page.html @@ -611,11 +623,11 @@ Özellikler apps/client/src/app/app.component.html - 78 + 79 apps/client/src/app/components/header/header.component.html - 341 + 351 apps/client/src/app/pages/features/features-page.html @@ -627,7 +639,7 @@ Sıkça Sorulan Sorular (SSS) apps/client/src/app/app.component.html - 82 + 83 apps/client/src/app/pages/about/overview/about-overview-page.html @@ -639,7 +651,7 @@ Lisans apps/client/src/app/app.component.html - 87 + 88 apps/client/src/app/pages/about/license/license-page.html @@ -651,19 +663,19 @@ Fiyatlandırma apps/client/src/app/app.component.html - 96 + 97 apps/client/src/app/components/header/header.component.html - 98 + 99 apps/client/src/app/components/header/header.component.html - 297 + 303 apps/client/src/app/components/header/header.component.html - 368 + 379 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -675,7 +687,7 @@ Gizlilik Politikası apps/client/src/app/app.component.html - 102 + 103 apps/client/src/app/pages/about/privacy-policy/privacy-policy-page.html @@ -687,7 +699,7 @@ Topluluk apps/client/src/app/app.component.html - 120 + 121 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -735,7 +747,7 @@ Alım satımda kayıp riski büyük boyutta olabilir. Kısa vadede ihtiyaç duyabileceğiniz parayla yatırım yapmak tavsiye edilmez. apps/client/src/app/app.component.html - 199 + 200 @@ -1351,7 +1363,7 @@ apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 83 + 135 apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html @@ -1767,7 +1779,7 @@ libs/ui/src/lib/assistant/assistant.html - 127 + 155 @@ -1879,7 +1891,7 @@ apps/client/src/app/components/header/header.component.html - 258 + 263 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1943,7 +1955,7 @@ apps/client/src/app/components/header/header.component.html - 224 + 229 @@ -2023,7 +2035,7 @@ apps/client/src/app/pages/portfolio/portfolio-page-routing.module.ts - 41 + 46 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -2055,7 +2067,7 @@ apps/client/src/app/components/header/header.component.html - 240 + 245 @@ -2067,7 +2079,7 @@ apps/client/src/app/components/header/header.component.html - 250 + 255 @@ -2079,7 +2091,7 @@ apps/client/src/app/components/header/header.component.html - 274 + 279 @@ -2087,7 +2099,7 @@ Ben apps/client/src/app/components/header/header.component.html - 206 + 211 @@ -2095,7 +2107,7 @@ Ghostfolio’m apps/client/src/app/components/header/header.component.html - 265 + 270 @@ -2103,7 +2115,7 @@ Ghostfolio Hakkında apps/client/src/app/components/header/header.component.html - 306 + 316 apps/client/src/app/pages/about/overview/about-overview-page.html @@ -2115,7 +2127,7 @@ Giriş apps/client/src/app/components/header/header.component.html - 397 + 412 apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html @@ -2127,7 +2139,7 @@ Haydi Başlayalım apps/client/src/app/components/header/header.component.html - 407 + 422 @@ -2139,7 +2151,7 @@ apps/client/src/app/components/header/header.component.ts - 229 + 230 @@ -2147,7 +2159,7 @@ Hay Allah! Güvenlik anahtarı yanlış. apps/client/src/app/components/header/header.component.ts - 244 + 245 apps/client/src/app/components/user-account-settings/user-account-settings.component.ts @@ -2159,7 +2171,7 @@ İşlemleri Yönet apps/client/src/app/components/home-holdings/home-holdings.html - 65 + 63 @@ -2458,8 +2470,8 @@ 89 - apps/client/src/app/pages/portfolio/fire/fire-page.html - 122 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 12 @@ -2614,8 +2626,8 @@ 84 - apps/client/src/app/pages/portfolio/fire/fire-page.html - 198 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 88 @@ -2655,7 +2667,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 203 + 213 @@ -2675,7 +2687,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 207 + 217 @@ -2691,7 +2703,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 211 + 221 @@ -2707,7 +2719,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 215 + 225 @@ -2719,7 +2731,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 230 + 240 @@ -2735,7 +2747,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 242 + 252 @@ -2759,7 +2771,7 @@ Üyeliğinizi Yükseltin apps/client/src/app/components/header/header.component.html - 180 + 185 apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html @@ -2771,7 +2783,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 278 + 288 @@ -2783,7 +2795,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 210 + 223 @@ -2795,7 +2807,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 220 + 233 @@ -2807,7 +2819,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 224 + 237 @@ -2819,7 +2831,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 246 + 259 @@ -2831,7 +2843,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 249 + 262 @@ -3343,7 +3355,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 306 + 324 @@ -3861,6 +3873,10 @@ apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html 32 + + libs/ui/src/lib/assistant/assistant.html + 127 + Load Dividends @@ -4210,14 +4226,6 @@ 351 - - FIRE - FIRE - - apps/client/src/app/pages/portfolio/fire/fire-page-routing.module.ts - 13 - - FIRE Finansal Özgürlük ve Erken Emeklilik (FIRE) @@ -4311,11 +4319,11 @@ apps/client/src/app/pages/pricing/pricing-page.html - 129 + 134 apps/client/src/app/pages/pricing/pricing-page.html - 191 + 201 @@ -4327,11 +4335,11 @@ apps/client/src/app/pages/pricing/pricing-page.html - 133 + 138 apps/client/src/app/pages/pricing/pricing-page.html - 195 + 205 @@ -4343,11 +4351,11 @@ apps/client/src/app/pages/pricing/pricing-page.html - 137 + 142 apps/client/src/app/pages/pricing/pricing-page.html - 199 + 209 @@ -4359,11 +4367,11 @@ apps/client/src/app/pages/pricing/pricing-page.html - 141 + 146 apps/client/src/app/pages/pricing/pricing-page.html - 219 + 229 @@ -4391,7 +4399,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 153 + 158 @@ -4399,7 +4407,7 @@ Alım satıma henüz başlamış yeni yatırımcılar için. apps/client/src/app/pages/pricing/pricing-page.html - 123 + 128 @@ -4407,11 +4415,11 @@ Eksiksiz yönetilen Ghostfolio bulut teklifi. apps/client/src/app/pages/pricing/pricing-page.html - 152 + 157 apps/client/src/app/pages/pricing/pricing-page.html - 251 + 261 @@ -4419,7 +4427,7 @@ Finansal varlıklarının tamamını görmeye ihtiyaç duyan hırslı yatırımcılar için. apps/client/src/app/pages/pricing/pricing-page.html - 184 + 194 @@ -4427,7 +4435,7 @@ E-posta ve Sohbet Desteği apps/client/src/app/pages/pricing/pricing-page.html - 247 + 257 @@ -4435,7 +4443,7 @@ Aboneliği Yenile apps/client/src/app/components/header/header.component.html - 186 + 191 apps/client/src/app/components/user-account-membership/user-account-membership.html @@ -4443,7 +4451,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 284 + 294 @@ -4451,7 +4459,7 @@ Tek seferlik ödeme, otomatik yenileme yok. apps/client/src/app/pages/pricing/pricing-page.html - 288 + 298 @@ -4467,7 +4475,7 @@ Ücretsiz. apps/client/src/app/pages/pricing/pricing-page.html - 309 + 327 @@ -4919,7 +4927,7 @@ Lütfen kupon kodunuzu girin: apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 115 + 124 @@ -4927,7 +4935,7 @@ Kupon kodu kullanılamadı apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 125 + 134 @@ -4935,7 +4943,7 @@ Kupon kodu kullanıldı apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 137 + 146 @@ -4943,7 +4951,7 @@ Yeniden Yükle apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 138 + 147 @@ -4979,7 +4987,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 264 + 274 @@ -4987,7 +4995,7 @@ Premium’u Deneyin apps/client/src/app/components/user-account-membership/user-account-membership.html - 41 + 51 @@ -4995,7 +5003,7 @@ Kupon Kullan apps/client/src/app/components/user-account-membership/user-account-membership.html - 55 + 65 @@ -5870,28 +5878,20 @@ 10 - - Ghostfolio X-ray uses static analysis to identify potential issues and risks in your portfolio. - Ghostfolio X-ray, portföyünüzdeki potansiyel sorunlar ve riskleri belirlemek için statik analiz yöntemleri uygulamaktadır. - - apps/client/src/app/pages/portfolio/fire/fire-page.html - 111 - - Currency Cluster Risks Kur Kümelenme Riskleri (Currency Cluster Risks) - apps/client/src/app/pages/portfolio/fire/fire-page.html - 141 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 31 Account Cluster Risks Hesap Kümelenme Riski (Account Cluster Risks) - apps/client/src/app/pages/portfolio/fire/fire-page.html - 160 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 50 @@ -5983,7 +5983,7 @@ Sahip olunan varlıkları bul... libs/ui/src/lib/assistant/assistant.component.ts - 139 + 144 @@ -6351,7 +6351,7 @@ Week to date libs/ui/src/lib/assistant/assistant.component.ts - 212 + 225 @@ -6359,7 +6359,7 @@ WTD libs/ui/src/lib/assistant/assistant.component.ts - 212 + 225 @@ -6367,7 +6367,7 @@ Month to date libs/ui/src/lib/assistant/assistant.component.ts - 216 + 229 @@ -6375,7 +6375,7 @@ MTD libs/ui/src/lib/assistant/assistant.component.ts - 216 + 229 @@ -6383,7 +6383,7 @@ Year to date libs/ui/src/lib/assistant/assistant.component.ts - 220 + 233 @@ -6419,7 +6419,7 @@ Reset Filters libs/ui/src/lib/assistant/assistant.html - 157 + 185 @@ -6427,7 +6427,7 @@ year libs/ui/src/lib/assistant/assistant.component.ts - 224 + 237 @@ -6435,7 +6435,7 @@ years libs/ui/src/lib/assistant/assistant.component.ts - 246 + 259 @@ -6443,7 +6443,7 @@ Asset Classes libs/ui/src/lib/assistant/assistant.html - 138 + 166 @@ -6451,7 +6451,7 @@ Apply Filters libs/ui/src/lib/assistant/assistant.html - 167 + 195 @@ -6727,7 +6727,7 @@ Table apps/client/src/app/components/home-holdings/home-holdings.html - 17 + 16 @@ -6735,7 +6735,7 @@ Chart apps/client/src/app/components/home-holdings/home-holdings.html - 20 + 19 @@ -7030,8 +7030,8 @@ Inactive Inactive - apps/client/src/app/pages/portfolio/fire/fire-page.html - 217 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 107 @@ -7095,7 +7095,7 @@ Threshold Min apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 9 + 54 @@ -7103,7 +7103,7 @@ Threshold Max apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 44 + 92 @@ -7111,7 +7111,7 @@ Close apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 77 + 129 @@ -7127,7 +7127,7 @@ No auto-renewal. apps/client/src/app/components/user-account-membership/user-account-membership.html - 62 + 72 @@ -7378,14 +7378,6 @@ 93 - - Allocation Cluster Risks - Allocation Cluster Risks - - apps/client/src/app/pages/portfolio/fire/fire-page.html - 179 - - Glossary Glossary @@ -7436,6 +7428,30 @@ 21 + + Threshold range + Threshold range + + apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html + 9 + + + + Ghostfolio X-ray uses static analysis to uncover potential issues and risks in your portfolio. Adjust the rules below and set custom thresholds to align with your personal investment strategy. + Ghostfolio X-ray uses static analysis to uncover potential issues and risks in your portfolio. Adjust the rules below and set custom thresholds to align with your personal investment strategy. + + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 5 + + + + Economic Market Cluster Risks + Economic Market Cluster Risks + + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 69 + + diff --git a/apps/client/src/locales/messages.xlf b/apps/client/src/locales/messages.xlf index fe58f08f..db47d712 100644 --- a/apps/client/src/locales/messages.xlf +++ b/apps/client/src/locales/messages.xlf @@ -7,27 +7,27 @@ snake-case apps/client/src/app/app.component.ts - 63 + 64 apps/client/src/app/app.component.ts - 65 + 66 apps/client/src/app/app.component.ts - 69 + 70 apps/client/src/app/app.component.ts - 73 + 74 apps/client/src/app/components/header/header.component.ts - 80 + 81 apps/client/src/app/components/header/header.component.ts - 85 + 86 apps/client/src/app/core/paths.ts @@ -95,7 +95,7 @@ snake-case apps/client/src/app/app.component.ts - 76 + 77 apps/client/src/app/core/paths.ts @@ -127,15 +127,15 @@ snake-case apps/client/src/app/app.component.ts - 77 + 78 apps/client/src/app/components/header/header.component.ts - 81 + 82 apps/client/src/app/components/header/header.component.ts - 86 + 87 apps/client/src/app/core/paths.ts @@ -173,13 +173,17 @@ apps/client/src/app/pages/blog/2023/11/hacktoberfest-2023-debriefing/hacktoberfest-2023-debriefing-page.component.ts 14 + + apps/client/src/app/pages/blog/2024/11/black-weeks-2024/black-weeks-2024-page.component.ts + 15 + apps/client/src/app/pages/faq/overview/faq-overview-page.component.ts 14 apps/client/src/app/pages/pricing/pricing-page.component.ts - 39 + 41 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -191,7 +195,7 @@ snake-case apps/client/src/app/app.component.ts - 70 + 71 apps/client/src/app/core/paths.ts @@ -207,15 +211,15 @@ snake-case apps/client/src/app/app.component.ts - 78 + 79 apps/client/src/app/components/header/header.component.ts - 82 + 83 apps/client/src/app/components/header/header.component.ts - 87 + 88 apps/client/src/app/core/paths.ts @@ -247,7 +251,7 @@ snake-case apps/client/src/app/app.component.ts - 79 + 80 apps/client/src/app/components/admin-settings/admin-settings.component.ts @@ -255,11 +259,11 @@ apps/client/src/app/components/header/header.component.ts - 83 + 84 apps/client/src/app/components/header/header.component.ts - 88 + 89 apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.component.ts @@ -267,7 +271,7 @@ apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 38 + 40 apps/client/src/app/core/http-response.interceptor.ts @@ -305,6 +309,10 @@ apps/client/src/app/pages/blog/2023/11/black-week-2023/black-week-2023-page.component.ts 16 + + apps/client/src/app/pages/blog/2024/11/black-weeks-2024/black-weeks-2024-page.component.ts + 16 + apps/client/src/app/pages/faq/saas/saas-page.component.ts 15 @@ -319,7 +327,7 @@ snake-case apps/client/src/app/app.component.ts - 74 + 75 apps/client/src/app/core/paths.ts @@ -335,11 +343,11 @@ snake-case apps/client/src/app/app.component.ts - 80 + 81 apps/client/src/app/components/header/header.component.ts - 89 + 90 apps/client/src/app/core/auth.guard.ts @@ -363,7 +371,7 @@ apps/client/src/app/pages/pricing/pricing-page.component.ts - 40 + 42 @@ -371,15 +379,15 @@ snake-case apps/client/src/app/app.component.ts - 81 + 82 apps/client/src/app/components/header/header.component.ts - 84 + 85 apps/client/src/app/components/header/header.component.ts - 90 + 91 apps/client/src/app/core/paths.ts @@ -456,18 +464,18 @@ Personal Finance apps/client/src/app/app.component.html - 56 + 57 Markets apps/client/src/app/app.component.html - 60 + 61 apps/client/src/app/components/header/header.component.html - 383 + 398 apps/client/src/app/components/home-market/home-market.html @@ -482,7 +490,7 @@ Resources apps/client/src/app/app.component.html - 63 + 64 apps/client/src/app/components/header/header.component.html @@ -490,7 +498,7 @@ apps/client/src/app/components/header/header.component.html - 286 + 291 apps/client/src/app/pages/resources/overview/resources-overview.component.html @@ -501,22 +509,22 @@ About apps/client/src/app/app.component.html - 69 + 70 apps/client/src/app/components/header/header.component.html - 112 + 117 apps/client/src/app/components/header/header.component.html - 354 + 364 Blog apps/client/src/app/app.component.html - 72 + 73 apps/client/src/app/pages/blog/2021/07/hallo-ghostfolio/hallo-ghostfolio-page.html @@ -598,6 +606,10 @@ apps/client/src/app/pages/blog/2024/09/hacktoberfest-2024/hacktoberfest-2024-page.html 187 + + apps/client/src/app/pages/blog/2024/11/black-weeks-2024/black-weeks-2024-page.html + 167 + apps/client/src/app/pages/blog/blog-page.html 5 @@ -607,7 +619,7 @@ Changelog apps/client/src/app/app.component.html - 76 + 77 apps/client/src/app/pages/about/changelog/changelog-page.html @@ -618,11 +630,11 @@ Features apps/client/src/app/app.component.html - 78 + 79 apps/client/src/app/components/header/header.component.html - 341 + 351 apps/client/src/app/pages/features/features-page.html @@ -633,7 +645,7 @@ Frequently Asked Questions (FAQ) apps/client/src/app/app.component.html - 82 + 83 apps/client/src/app/pages/about/overview/about-overview-page.html @@ -644,7 +656,7 @@ License apps/client/src/app/app.component.html - 87 + 88 apps/client/src/app/pages/about/license/license-page.html @@ -655,19 +667,19 @@ Pricing apps/client/src/app/app.component.html - 96 + 97 apps/client/src/app/components/header/header.component.html - 98 + 99 apps/client/src/app/components/header/header.component.html - 297 + 303 apps/client/src/app/components/header/header.component.html - 368 + 379 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -678,7 +690,7 @@ Privacy Policy apps/client/src/app/app.component.html - 102 + 103 apps/client/src/app/pages/about/privacy-policy/privacy-policy-page.html @@ -689,7 +701,7 @@ Community apps/client/src/app/app.component.html - 120 + 121 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -736,7 +748,7 @@ The risk of loss in trading can be substantial. It is not advisable to invest money you may need in the short term. apps/client/src/app/app.component.html - 199 + 200 @@ -1336,7 +1348,7 @@ apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 83 + 135 apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html @@ -1865,7 +1877,7 @@ apps/client/src/app/components/header/header.component.html - 258 + 263 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1928,7 +1940,7 @@ libs/ui/src/lib/assistant/assistant.html - 127 + 155 @@ -2041,7 +2053,7 @@ apps/client/src/app/pages/portfolio/portfolio-page-routing.module.ts - 41 + 46 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -2070,7 +2082,7 @@ apps/client/src/app/components/header/header.component.html - 240 + 245 @@ -2081,7 +2093,7 @@ apps/client/src/app/components/header/header.component.html - 250 + 255 @@ -2092,14 +2104,14 @@ apps/client/src/app/components/header/header.component.html - 274 + 279 Me apps/client/src/app/components/header/header.component.html - 206 + 211 @@ -2110,21 +2122,21 @@ apps/client/src/app/components/header/header.component.html - 224 + 229 My Ghostfolio apps/client/src/app/components/header/header.component.html - 265 + 270 About Ghostfolio apps/client/src/app/components/header/header.component.html - 306 + 316 apps/client/src/app/pages/about/overview/about-overview-page.html @@ -2135,7 +2147,7 @@ Sign in apps/client/src/app/components/header/header.component.html - 397 + 412 apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html @@ -2146,7 +2158,7 @@ Get started apps/client/src/app/components/header/header.component.html - 407 + 422 @@ -2157,14 +2169,14 @@ apps/client/src/app/components/header/header.component.ts - 229 + 230 Oops! Incorrect Security Token. apps/client/src/app/components/header/header.component.ts - 244 + 245 apps/client/src/app/components/user-account-settings/user-account-settings.component.ts @@ -2175,7 +2187,7 @@ Manage Activities apps/client/src/app/components/home-holdings/home-holdings.html - 65 + 63 @@ -2415,8 +2427,8 @@ 84 - apps/client/src/app/pages/portfolio/fire/fire-page.html - 198 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 88 @@ -2458,8 +2470,8 @@ 89 - apps/client/src/app/pages/portfolio/fire/fire-page.html - 122 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 12 @@ -2628,7 +2640,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 203 + 213 @@ -2647,7 +2659,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 207 + 217 @@ -2662,7 +2674,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 211 + 221 @@ -2677,7 +2689,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 215 + 225 @@ -2688,7 +2700,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 230 + 240 @@ -2703,7 +2715,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 242 + 252 @@ -2724,7 +2736,7 @@ Upgrade Plan apps/client/src/app/components/header/header.component.html - 180 + 185 apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html @@ -2736,7 +2748,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 278 + 288 @@ -2747,7 +2759,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 210 + 223 @@ -2758,7 +2770,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 220 + 233 @@ -2769,7 +2781,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 224 + 237 @@ -2780,7 +2792,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 246 + 259 @@ -2791,7 +2803,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 249 + 262 @@ -2819,28 +2831,28 @@ Please enter your coupon code: apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 115 + 124 Could not redeem coupon code apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 125 + 134 Coupon code has been redeemed apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 137 + 146 Reload apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 138 + 147 @@ -2851,21 +2863,21 @@ apps/client/src/app/pages/pricing/pricing-page.html - 264 + 274 Try Premium apps/client/src/app/components/user-account-membership/user-account-membership.html - 41 + 51 Redeem Coupon apps/client/src/app/components/user-account-membership/user-account-membership.html - 55 + 65 @@ -3522,7 +3534,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 306 + 324 @@ -4034,6 +4046,10 @@ apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html 32 + + libs/ui/src/lib/assistant/assistant.html + 127 + Load Dividends @@ -4338,13 +4354,6 @@ 351 - - FIRE - - apps/client/src/app/pages/portfolio/fire/fire-page-routing.module.ts - 13 - - FIRE @@ -4366,25 +4375,18 @@ 40 - - Ghostfolio X-ray uses static analysis to identify potential issues and risks in your portfolio. - - apps/client/src/app/pages/portfolio/fire/fire-page.html - 111 - - Currency Cluster Risks - apps/client/src/app/pages/portfolio/fire/fire-page.html - 141 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 31 Account Cluster Risks - apps/client/src/app/pages/portfolio/fire/fire-page.html - 160 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 50 @@ -4449,11 +4451,11 @@ apps/client/src/app/pages/pricing/pricing-page.html - 129 + 134 apps/client/src/app/pages/pricing/pricing-page.html - 191 + 201 @@ -4464,11 +4466,11 @@ apps/client/src/app/pages/pricing/pricing-page.html - 133 + 138 apps/client/src/app/pages/pricing/pricing-page.html - 195 + 205 @@ -4479,11 +4481,11 @@ apps/client/src/app/pages/pricing/pricing-page.html - 137 + 142 apps/client/src/app/pages/pricing/pricing-page.html - 199 + 209 @@ -4494,11 +4496,11 @@ apps/client/src/app/pages/pricing/pricing-page.html - 141 + 146 apps/client/src/app/pages/pricing/pricing-page.html - 219 + 229 @@ -4523,46 +4525,46 @@ apps/client/src/app/pages/pricing/pricing-page.html - 153 + 158 For new investors who are just getting started with trading. apps/client/src/app/pages/pricing/pricing-page.html - 123 + 128 Fully managed Ghostfolio cloud offering. apps/client/src/app/pages/pricing/pricing-page.html - 152 + 157 apps/client/src/app/pages/pricing/pricing-page.html - 251 + 261 For ambitious investors who need the full picture of their financial assets. apps/client/src/app/pages/pricing/pricing-page.html - 184 + 194 Email and Chat Support apps/client/src/app/pages/pricing/pricing-page.html - 247 + 257 Renew Plan apps/client/src/app/components/header/header.component.html - 186 + 191 apps/client/src/app/components/user-account-membership/user-account-membership.html @@ -4570,14 +4572,14 @@ apps/client/src/app/pages/pricing/pricing-page.html - 284 + 294 One-time payment, no auto-renewal. apps/client/src/app/pages/pricing/pricing-page.html - 288 + 298 @@ -4591,7 +4593,7 @@ It’s free. apps/client/src/app/pages/pricing/pricing-page.html - 309 + 327 @@ -5111,7 +5113,7 @@ Find holding... libs/ui/src/lib/assistant/assistant.component.ts - 139 + 144 @@ -5774,35 +5776,35 @@ Year to date libs/ui/src/lib/assistant/assistant.component.ts - 220 + 233 Week to date libs/ui/src/lib/assistant/assistant.component.ts - 212 + 225 Month to date libs/ui/src/lib/assistant/assistant.component.ts - 216 + 229 MTD libs/ui/src/lib/assistant/assistant.component.ts - 216 + 229 WTD libs/ui/src/lib/assistant/assistant.component.ts - 212 + 225 @@ -5827,7 +5829,7 @@ Reset Filters libs/ui/src/lib/assistant/assistant.html - 157 + 185 @@ -5841,28 +5843,28 @@ year libs/ui/src/lib/assistant/assistant.component.ts - 224 + 237 years libs/ui/src/lib/assistant/assistant.component.ts - 246 + 259 Apply Filters libs/ui/src/lib/assistant/assistant.html - 167 + 195 Asset Classes libs/ui/src/lib/assistant/assistant.html - 138 + 166 @@ -6105,14 +6107,14 @@ Chart apps/client/src/app/components/home-holdings/home-holdings.html - 20 + 19 Table apps/client/src/app/components/home-holdings/home-holdings.html - 17 + 16 @@ -6370,8 +6372,8 @@ Inactive - apps/client/src/app/pages/portfolio/fire/fire-page.html - 217 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 107 @@ -6399,7 +6401,7 @@ Threshold Max apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 44 + 92 @@ -6420,7 +6422,7 @@ Threshold Min apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 9 + 54 @@ -6448,14 +6450,14 @@ Close apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 77 + 129 No auto-renewal. apps/client/src/app/components/user-account-membership/user-account-membership.html - 62 + 72 @@ -6714,13 +6716,6 @@ 28 - - Allocation Cluster Risks - - apps/client/src/app/pages/portfolio/fire/fire-page.html - 179 - - Skip @@ -6728,6 +6723,27 @@ 83 + + Threshold range + + apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html + 9 + + + + Economic Market Cluster Risks + + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 69 + + + + Ghostfolio X-ray uses static analysis to uncover potential issues and risks in your portfolio. Adjust the rules below and set custom thresholds to align with your personal investment strategy. + + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 5 + + diff --git a/apps/client/src/locales/messages.zh.xlf b/apps/client/src/locales/messages.zh.xlf index 876bcd84..a1c434da 100644 --- a/apps/client/src/locales/messages.zh.xlf +++ b/apps/client/src/locales/messages.zh.xlf @@ -8,27 +8,27 @@ snake-case apps/client/src/app/app.component.ts - 63 + 64 apps/client/src/app/app.component.ts - 65 + 66 apps/client/src/app/app.component.ts - 69 + 70 apps/client/src/app/app.component.ts - 73 + 74 apps/client/src/app/components/header/header.component.ts - 80 + 81 apps/client/src/app/components/header/header.component.ts - 85 + 86 apps/client/src/app/core/paths.ts @@ -97,7 +97,7 @@ snake-case apps/client/src/app/app.component.ts - 76 + 77 apps/client/src/app/core/paths.ts @@ -130,15 +130,15 @@ snake-case apps/client/src/app/app.component.ts - 77 + 78 apps/client/src/app/components/header/header.component.ts - 81 + 82 apps/client/src/app/components/header/header.component.ts - 86 + 87 apps/client/src/app/core/paths.ts @@ -176,13 +176,17 @@ apps/client/src/app/pages/blog/2023/11/hacktoberfest-2023-debriefing/hacktoberfest-2023-debriefing-page.component.ts 14 + + apps/client/src/app/pages/blog/2024/11/black-weeks-2024/black-weeks-2024-page.component.ts + 15 + apps/client/src/app/pages/faq/overview/faq-overview-page.component.ts 14 apps/client/src/app/pages/pricing/pricing-page.component.ts - 39 + 41 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -195,7 +199,7 @@ snake-case apps/client/src/app/app.component.ts - 70 + 71 apps/client/src/app/core/paths.ts @@ -212,15 +216,15 @@ snake-case apps/client/src/app/app.component.ts - 78 + 79 apps/client/src/app/components/header/header.component.ts - 82 + 83 apps/client/src/app/components/header/header.component.ts - 87 + 88 apps/client/src/app/core/paths.ts @@ -253,7 +257,7 @@ snake-case apps/client/src/app/app.component.ts - 79 + 80 apps/client/src/app/components/admin-settings/admin-settings.component.ts @@ -261,11 +265,11 @@ apps/client/src/app/components/header/header.component.ts - 83 + 84 apps/client/src/app/components/header/header.component.ts - 88 + 89 apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.component.ts @@ -273,7 +277,7 @@ apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 38 + 40 apps/client/src/app/core/http-response.interceptor.ts @@ -311,6 +315,10 @@ apps/client/src/app/pages/blog/2023/11/black-week-2023/black-week-2023-page.component.ts 16 + + apps/client/src/app/pages/blog/2024/11/black-weeks-2024/black-weeks-2024-page.component.ts + 16 + apps/client/src/app/pages/faq/saas/saas-page.component.ts 15 @@ -326,7 +334,7 @@ snake-case apps/client/src/app/app.component.ts - 74 + 75 apps/client/src/app/core/paths.ts @@ -343,11 +351,11 @@ snake-case apps/client/src/app/app.component.ts - 80 + 81 apps/client/src/app/components/header/header.component.ts - 89 + 90 apps/client/src/app/core/auth.guard.ts @@ -371,7 +379,7 @@ apps/client/src/app/pages/pricing/pricing-page.component.ts - 40 + 42 @@ -380,15 +388,15 @@ snake-case apps/client/src/app/app.component.ts - 81 + 82 apps/client/src/app/components/header/header.component.ts - 84 + 85 apps/client/src/app/components/header/header.component.ts - 90 + 91 apps/client/src/app/core/paths.ts @@ -468,7 +476,7 @@ 个人财务 apps/client/src/app/app.component.html - 56 + 57 @@ -476,11 +484,11 @@ 市场 apps/client/src/app/app.component.html - 60 + 61 apps/client/src/app/components/header/header.component.html - 383 + 398 apps/client/src/app/components/home-market/home-market.html @@ -496,7 +504,7 @@ 资源 apps/client/src/app/app.component.html - 63 + 64 apps/client/src/app/components/header/header.component.html @@ -504,7 +512,7 @@ apps/client/src/app/components/header/header.component.html - 286 + 291 apps/client/src/app/pages/resources/overview/resources-overview.component.html @@ -516,15 +524,15 @@ 关于 apps/client/src/app/app.component.html - 69 + 70 apps/client/src/app/components/header/header.component.html - 112 + 117 apps/client/src/app/components/header/header.component.html - 354 + 364 @@ -532,7 +540,7 @@ 博客 apps/client/src/app/app.component.html - 72 + 73 apps/client/src/app/pages/blog/2021/07/hallo-ghostfolio/hallo-ghostfolio-page.html @@ -614,6 +622,10 @@ apps/client/src/app/pages/blog/2024/09/hacktoberfest-2024/hacktoberfest-2024-page.html 187 + + apps/client/src/app/pages/blog/2024/11/black-weeks-2024/black-weeks-2024-page.html + 167 + apps/client/src/app/pages/blog/blog-page.html 5 @@ -624,7 +636,7 @@ 变更日志 apps/client/src/app/app.component.html - 76 + 77 apps/client/src/app/pages/about/changelog/changelog-page.html @@ -636,11 +648,11 @@ 功能 apps/client/src/app/app.component.html - 78 + 79 apps/client/src/app/components/header/header.component.html - 341 + 351 apps/client/src/app/pages/features/features-page.html @@ -652,7 +664,7 @@ 常见问题 (FAQ) apps/client/src/app/app.component.html - 82 + 83 apps/client/src/app/pages/about/overview/about-overview-page.html @@ -664,7 +676,7 @@ 许可 apps/client/src/app/app.component.html - 87 + 88 apps/client/src/app/pages/about/license/license-page.html @@ -676,19 +688,19 @@ 价钱 apps/client/src/app/app.component.html - 96 + 97 apps/client/src/app/components/header/header.component.html - 98 + 99 apps/client/src/app/components/header/header.component.html - 297 + 303 apps/client/src/app/components/header/header.component.html - 368 + 379 apps/client/src/app/pages/resources/personal-finance-tools/product-page.html @@ -700,7 +712,7 @@ 隐私政策 apps/client/src/app/app.component.html - 102 + 103 apps/client/src/app/pages/about/privacy-policy/privacy-policy-page.html @@ -712,7 +724,7 @@ 社区 apps/client/src/app/app.component.html - 120 + 121 apps/client/src/app/components/user-account-settings/user-account-settings.html @@ -760,7 +772,7 @@ 交易损失的风险可能很大。不建议将短期内可能需要的资金进行投资。 apps/client/src/app/app.component.html - 199 + 200 @@ -1396,7 +1408,7 @@ apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 83 + 135 apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html @@ -1980,7 +1992,7 @@ apps/client/src/app/components/header/header.component.html - 258 + 263 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -2048,7 +2060,7 @@ libs/ui/src/lib/assistant/assistant.html - 127 + 155 @@ -2176,7 +2188,7 @@ apps/client/src/app/pages/portfolio/portfolio-page-routing.module.ts - 41 + 46 apps/client/src/app/pages/resources/personal-finance-tools/product-page.component.ts @@ -2208,7 +2220,7 @@ apps/client/src/app/components/header/header.component.html - 240 + 245 @@ -2220,7 +2232,7 @@ apps/client/src/app/components/header/header.component.html - 250 + 255 @@ -2232,7 +2244,7 @@ apps/client/src/app/components/header/header.component.html - 274 + 279 @@ -2240,7 +2252,7 @@ apps/client/src/app/components/header/header.component.html - 206 + 211 @@ -2252,7 +2264,7 @@ apps/client/src/app/components/header/header.component.html - 224 + 229 @@ -2260,7 +2272,7 @@ 我的 Ghostfolio apps/client/src/app/components/header/header.component.html - 265 + 270 @@ -2268,7 +2280,7 @@ 关于 Ghostfolio apps/client/src/app/components/header/header.component.html - 306 + 316 apps/client/src/app/pages/about/overview/about-overview-page.html @@ -2280,7 +2292,7 @@ 登入 apps/client/src/app/components/header/header.component.html - 397 + 412 apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html @@ -2292,7 +2304,7 @@ 开始使用 apps/client/src/app/components/header/header.component.html - 407 + 422 @@ -2304,7 +2316,7 @@ apps/client/src/app/components/header/header.component.ts - 229 + 230 @@ -2312,7 +2324,7 @@ 哎呀!安全令牌不正确。 apps/client/src/app/components/header/header.component.ts - 244 + 245 apps/client/src/app/components/user-account-settings/user-account-settings.component.ts @@ -2324,7 +2336,7 @@ 管理活动 apps/client/src/app/components/home-holdings/home-holdings.html - 65 + 63 @@ -2591,8 +2603,8 @@ 84 - apps/client/src/app/pages/portfolio/fire/fire-page.html - 198 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 88 @@ -2639,8 +2651,8 @@ 89 - apps/client/src/app/pages/portfolio/fire/fire-page.html - 122 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 12 @@ -2828,7 +2840,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 203 + 213 @@ -2848,7 +2860,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 207 + 217 @@ -2864,7 +2876,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 211 + 221 @@ -2880,7 +2892,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 215 + 225 @@ -2892,7 +2904,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 230 + 240 @@ -2908,7 +2920,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 242 + 252 @@ -2932,7 +2944,7 @@ 升级计划 apps/client/src/app/components/header/header.component.html - 180 + 185 apps/client/src/app/components/subscription-interstitial-dialog/subscription-interstitial-dialog.html @@ -2944,7 +2956,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 278 + 288 @@ -2956,7 +2968,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 210 + 223 @@ -2968,7 +2980,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 220 + 233 @@ -2980,7 +2992,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 224 + 237 @@ -2992,7 +3004,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 246 + 259 @@ -3004,7 +3016,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 249 + 262 @@ -3036,7 +3048,7 @@ 请输入您的优惠券代码: apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 115 + 124 @@ -3044,7 +3056,7 @@ 无法兑换优惠券代码 apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 125 + 134 @@ -3052,7 +3064,7 @@ 优惠券代码已兑换 apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 137 + 146 @@ -3060,7 +3072,7 @@ 重新加载 apps/client/src/app/components/user-account-membership/user-account-membership.component.ts - 138 + 147 @@ -3072,7 +3084,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 264 + 274 @@ -3080,7 +3092,7 @@ 尝试高级版 apps/client/src/app/components/user-account-membership/user-account-membership.html - 41 + 51 @@ -3088,7 +3100,7 @@ 兑换优惠券 apps/client/src/app/components/user-account-membership/user-account-membership.html - 55 + 65 @@ -3820,7 +3832,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 306 + 324 @@ -4398,6 +4410,10 @@ apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html 32 + + libs/ui/src/lib/assistant/assistant.html + 127 + Load Dividends @@ -4739,14 +4755,6 @@ 351 - - FIRE - 财务独立 - - apps/client/src/app/pages/portfolio/fire/fire-page-routing.module.ts - 13 - - FIRE 财务独立 @@ -4771,28 +4779,20 @@ 40 - - Ghostfolio X-ray uses static analysis to identify potential issues and risks in your portfolio. - Ghostfolio X-ray 使用静态分析来识别您的投资组合中的潜在问题和风险。 - - apps/client/src/app/pages/portfolio/fire/fire-page.html - 111 - - Currency Cluster Risks 货币集群风险 - apps/client/src/app/pages/portfolio/fire/fire-page.html - 141 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 31 Account Cluster Risks 账户集群风险 - apps/client/src/app/pages/portfolio/fire/fire-page.html - 160 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 50 @@ -4864,11 +4864,11 @@ apps/client/src/app/pages/pricing/pricing-page.html - 129 + 134 apps/client/src/app/pages/pricing/pricing-page.html - 191 + 201 @@ -4880,11 +4880,11 @@ apps/client/src/app/pages/pricing/pricing-page.html - 133 + 138 apps/client/src/app/pages/pricing/pricing-page.html - 195 + 205 @@ -4896,11 +4896,11 @@ apps/client/src/app/pages/pricing/pricing-page.html - 137 + 142 apps/client/src/app/pages/pricing/pricing-page.html - 199 + 209 @@ -4912,11 +4912,11 @@ apps/client/src/app/pages/pricing/pricing-page.html - 141 + 146 apps/client/src/app/pages/pricing/pricing-page.html - 219 + 229 @@ -4944,7 +4944,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 153 + 158 @@ -4952,7 +4952,7 @@ 适合刚开始交易的新投资者。 apps/client/src/app/pages/pricing/pricing-page.html - 123 + 128 @@ -4960,11 +4960,11 @@ 完全托管的 Ghostfolio 云产品。 apps/client/src/app/pages/pricing/pricing-page.html - 152 + 157 apps/client/src/app/pages/pricing/pricing-page.html - 251 + 261 @@ -4972,7 +4972,7 @@ 适合需要全面了解其金融资产的雄心勃勃的投资者。 apps/client/src/app/pages/pricing/pricing-page.html - 184 + 194 @@ -4980,7 +4980,7 @@ 电子邮件和聊天支持 apps/client/src/app/pages/pricing/pricing-page.html - 247 + 257 @@ -4988,7 +4988,7 @@ 更新计划 apps/client/src/app/components/header/header.component.html - 186 + 191 apps/client/src/app/components/user-account-membership/user-account-membership.html @@ -4996,7 +4996,7 @@ apps/client/src/app/pages/pricing/pricing-page.html - 284 + 294 @@ -5004,7 +5004,7 @@ 一次性付款,无自动续订。 apps/client/src/app/pages/pricing/pricing-page.html - 288 + 298 @@ -5020,7 +5020,7 @@ 免费。 apps/client/src/app/pages/pricing/pricing-page.html - 309 + 327 @@ -5600,7 +5600,7 @@ 查找持有... libs/ui/src/lib/assistant/assistant.component.ts - 139 + 144 @@ -6352,7 +6352,7 @@ 今年迄今为止 libs/ui/src/lib/assistant/assistant.component.ts - 220 + 233 @@ -6360,7 +6360,7 @@ 本周至今 libs/ui/src/lib/assistant/assistant.component.ts - 212 + 225 @@ -6368,7 +6368,7 @@ 本月至今 libs/ui/src/lib/assistant/assistant.component.ts - 216 + 229 @@ -6376,7 +6376,7 @@ 最大输运量 libs/ui/src/lib/assistant/assistant.component.ts - 216 + 229 @@ -6384,7 +6384,7 @@ 世界贸易组织 libs/ui/src/lib/assistant/assistant.component.ts - 212 + 225 @@ -6412,7 +6412,7 @@ 重置过滤器 libs/ui/src/lib/assistant/assistant.html - 157 + 185 @@ -6428,7 +6428,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 224 + 237 @@ -6436,7 +6436,7 @@ libs/ui/src/lib/assistant/assistant.component.ts - 246 + 259 @@ -6444,7 +6444,7 @@ 应用过滤器 libs/ui/src/lib/assistant/assistant.html - 167 + 195 @@ -6452,7 +6452,7 @@ 资产类别 libs/ui/src/lib/assistant/assistant.html - 138 + 166 @@ -6728,7 +6728,7 @@ Table apps/client/src/app/components/home-holdings/home-holdings.html - 17 + 16 @@ -6736,7 +6736,7 @@ Chart apps/client/src/app/components/home-holdings/home-holdings.html - 20 + 19 @@ -7031,8 +7031,8 @@ Inactive Inactive - apps/client/src/app/pages/portfolio/fire/fire-page.html - 217 + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 107 @@ -7096,7 +7096,7 @@ Threshold Min apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 9 + 54 @@ -7104,7 +7104,7 @@ Threshold Max apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 44 + 92 @@ -7112,7 +7112,7 @@ Close apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html - 77 + 129 @@ -7128,7 +7128,7 @@ No auto-renewal. apps/client/src/app/components/user-account-membership/user-account-membership.html - 62 + 72 @@ -7379,14 +7379,6 @@ 93 - - Allocation Cluster Risks - Allocation Cluster Risks - - apps/client/src/app/pages/portfolio/fire/fire-page.html - 179 - - Glossary Glossary @@ -7437,6 +7429,30 @@ 21 + + Threshold range + Threshold range + + apps/client/src/app/components/rule/rule-settings-dialog/rule-settings-dialog.html + 9 + + + + Ghostfolio X-ray uses static analysis to uncover potential issues and risks in your portfolio. Adjust the rules below and set custom thresholds to align with your personal investment strategy. + Ghostfolio X-ray uses static analysis to uncover potential issues and risks in your portfolio. Adjust the rules below and set custom thresholds to align with your personal investment strategy. + + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 5 + + + + Economic Market Cluster Risks + Economic Market Cluster Risks + + apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html + 69 + + From 4b0e75b26cacae2696c40e3b5ea9710e14f388b7 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 16 Nov 2024 18:46:37 +0100 Subject: [PATCH 37/65] Release/2.123.0 (#4052) * Release 2.123.0 * Update changelog --- CHANGELOG.md | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b57b6f9b..7c31eb91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ 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.123.0 - 2024-11-16 ### Added diff --git a/package-lock.json b/package-lock.json index be881d36..580f6236 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ghostfolio", - "version": "2.122.0", + "version": "2.123.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ghostfolio", - "version": "2.122.0", + "version": "2.123.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/package.json b/package.json index 34f5a4fa..8719f2a7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ghostfolio", - "version": "2.122.0", + "version": "2.123.0", "homepage": "https://ghostfol.io", "license": "AGPL-3.0", "repository": "https://github.com/ghostfolio/ghostfolio", From e7356dc1704ae93479a673f7a82e9fd3f626d7db Mon Sep 17 00:00:00 2001 From: QURBAN AHMAD <79472606+qur786@users.noreply.github.com> Date: Sun, 17 Nov 2024 14:35:59 +0530 Subject: [PATCH 38/65] Feature/add pagination support to user endpoint (#4050) * Add pagination support to user endpoint * Update changelog --- CHANGELOG.md | 6 +++++ apps/api/src/app/admin/admin.controller.ts | 10 ++++++-- apps/api/src/app/admin/admin.service.ts | 23 +++++++++++++++---- apps/client/src/app/services/admin.service.ts | 6 ++++- 4 files changed, 37 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c31eb91..68096486 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ 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 + +### Added + +- Added pagination parameters (`skip`, `take`) to the endpoint `GET api/v1/admin/user` + ## 2.123.0 - 2024-11-16 ### Added diff --git a/apps/api/src/app/admin/admin.controller.ts b/apps/api/src/app/admin/admin.controller.ts index e0444d11..4cc4d467 100644 --- a/apps/api/src/app/admin/admin.controller.ts +++ b/apps/api/src/app/admin/admin.controller.ts @@ -352,7 +352,13 @@ export class AdminController { @Get('user') @HasPermission(permissions.accessAdminControl) @UseGuards(AuthGuard('jwt'), HasPermissionGuard) - public async getUsers(): Promise { - return this.adminService.getUsers(); + public async getUsers( + @Query('skip') skip?: number, + @Query('take') take?: number + ): Promise { + return this.adminService.getUsers({ + skip: isNaN(skip) ? undefined : skip, + take: isNaN(take) ? undefined : take + }); } } diff --git a/apps/api/src/app/admin/admin.service.ts b/apps/api/src/app/admin/admin.service.ts index 49964c77..a0531c48 100644 --- a/apps/api/src/app/admin/admin.service.ts +++ b/apps/api/src/app/admin/admin.service.ts @@ -429,8 +429,14 @@ export class AdminService { }; } - public async getUsers(): Promise { - return { users: await this.getUsersWithAnalytics() }; + public async getUsers({ + skip, + take = Number.MAX_SAFE_INTEGER + }: { + skip?: number; + take?: number; + }): Promise { + return { users: await this.getUsersWithAnalytics({ skip, take }) }; } public async patchAssetProfileData({ @@ -640,7 +646,13 @@ export class AdminService { return { marketData, count: marketData.length }; } - private async getUsersWithAnalytics(): Promise { + private async getUsersWithAnalytics({ + skip, + take + }: { + skip?: number; + take?: number; + }): Promise { let orderBy: Prisma.UserOrderByWithRelationInput = { createdAt: 'desc' }; @@ -661,6 +673,8 @@ export class AdminService { const usersWithAnalytics = await this.prismaService.user.findMany({ orderBy, + skip, + take, where, select: { _count: { @@ -677,8 +691,7 @@ export class AdminService { id: true, role: true, Subscription: true - }, - take: 30 + } }); return usersWithAnalytics.map( diff --git a/apps/client/src/app/services/admin.service.ts b/apps/client/src/app/services/admin.service.ts index 4c011e8c..367c1b43 100644 --- a/apps/client/src/app/services/admin.service.ts +++ b/apps/client/src/app/services/admin.service.ts @@ -157,7 +157,11 @@ export class AdminService { } public fetchUsers() { - return this.http.get('/api/v1/admin/user'); + let params = new HttpParams(); + + params = params.append('take', 100); + + return this.http.get('/api/v1/admin/user', { params }); } public gather7Days() { From 2568fe4828c4c88adac64204325f15d20bacf8ca Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 18 Nov 2024 12:00:09 +0100 Subject: [PATCH 39/65] Feature/upgrade nx to version 20.1.2 (#4054) * Upgrade Nx to version 20.1.2 * Update changelog --- CHANGELOG.md | 4 + package-lock.json | 636 +++++++++++++++++++++++++--------------------- package.json | 22 +- 3 files changed, 363 insertions(+), 299 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 68096486..3d6e5eb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added pagination parameters (`skip`, `take`) to the endpoint `GET api/v1/admin/user` +### Changed + +- Upgraded `Nx` from version `20.0.6` to `20.1.2` + ## 2.123.0 - 2024-11-16 ### Added diff --git a/package-lock.json b/package-lock.json index 580f6236..e51ca278 100644 --- a/package-lock.json +++ b/package-lock.json @@ -107,16 +107,16 @@ "@angular/pwa": "18.2.9", "@nestjs/schematics": "10.0.1", "@nestjs/testing": "10.1.3", - "@nx/angular": "20.0.6", - "@nx/cypress": "20.0.6", - "@nx/eslint-plugin": "20.0.6", - "@nx/jest": "20.0.6", - "@nx/js": "20.0.6", - "@nx/nest": "20.0.6", - "@nx/node": "20.0.6", - "@nx/storybook": "20.0.6", - "@nx/web": "20.0.6", - "@nx/workspace": "20.0.6", + "@nx/angular": "20.1.2", + "@nx/cypress": "20.1.2", + "@nx/eslint-plugin": "20.1.2", + "@nx/jest": "20.1.2", + "@nx/js": "20.1.2", + "@nx/nest": "20.1.2", + "@nx/node": "20.1.2", + "@nx/storybook": "20.1.2", + "@nx/web": "20.1.2", + "@nx/workspace": "20.1.2", "@schematics/angular": "18.2.9", "@simplewebauthn/types": "9.0.1", "@storybook/addon-essentials": "8.3.6", @@ -147,7 +147,7 @@ "jest": "29.7.0", "jest-environment-jsdom": "29.7.0", "jest-preset-angular": "14.1.0", - "nx": "20.0.6", + "nx": "20.1.2", "prettier": "3.3.3", "prettier-plugin-organize-attributes": "1.0.0", "prisma": "5.22.0", @@ -170,7 +170,7 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.0.tgz", "integrity": "sha512-Ff9+ksdQQB3rMncgqDK78uLznstjyfIf2Arnh22pW8kBpLs6rpKDwgnZT46hin5Hl1WzazzK64DOrhSwYpS7bQ==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/@ampproject/remapping": { @@ -5199,25 +5199,25 @@ } }, "node_modules/@module-federation/bridge-react-webpack-plugin": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/@module-federation/bridge-react-webpack-plugin/-/bridge-react-webpack-plugin-0.6.6.tgz", - "integrity": "sha512-NANaSOKem+1t/Fbd1GjXnStJRe7O33ya+FR/yYkTUd1H5hmlzVDNo/lYxYuUl3O/gH9Lnlr2Gf9unyWoIW0wHw==", + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/@module-federation/bridge-react-webpack-plugin/-/bridge-react-webpack-plugin-0.6.9.tgz", + "integrity": "sha512-KXTPO0vkrtHEIcthU3TIQEkPxoytcmdyNXRwOojZEVQhqEefykAek48ndFiVTmyOu2LW2EuzP49Le8zY7nESWQ==", "dev": true, "dependencies": { - "@module-federation/sdk": "0.6.6", + "@module-federation/sdk": "0.6.9", "@types/semver": "7.5.8", "semver": "7.6.3" } }, "node_modules/@module-federation/data-prefetch": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/@module-federation/data-prefetch/-/data-prefetch-0.6.6.tgz", - "integrity": "sha512-rakEHrg2pqbOqJ3uWT2p3kgTCOxBQdEIqmew3XBAXTZ0NblZtkXeMHupcW/W6+ccvbPdn/T/PSICx9HHSvfEVg==", + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/@module-federation/data-prefetch/-/data-prefetch-0.6.9.tgz", + "integrity": "sha512-rpHxfHNkIiPA441GzXI6TMYjSrUjRWDwxJTvRQopX/P0jK5vKtNwT1UBTNF2DJkbtO1idljfhbrIufEg0OY72w==", "dev": true, "license": "MIT", "dependencies": { - "@module-federation/runtime": "0.6.6", - "@module-federation/sdk": "0.6.6", + "@module-federation/runtime": "0.6.9", + "@module-federation/sdk": "0.6.9", "fs-extra": "9.1.0" }, "peerDependencies": { @@ -5226,15 +5226,15 @@ } }, "node_modules/@module-federation/dts-plugin": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/@module-federation/dts-plugin/-/dts-plugin-0.6.6.tgz", - "integrity": "sha512-sNCghGgrpCOOVk2xpzgAGAFeo2ONcv6eAnEfe7Q2gD7R6NrGgOrB5KVhN/uWIzFJG8tqNfSSjam+woTyrrayfg==", + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/@module-federation/dts-plugin/-/dts-plugin-0.6.9.tgz", + "integrity": "sha512-uiMjjEFcMlOvRtNu8/tt7sJ5y7WTosTVym0V7lMQjgoeX0QesvZqRhgzw5gQcPcFvbk54RwTUI2rS8OEGScCFw==", "dev": true, "license": "MIT", "dependencies": { - "@module-federation/managers": "0.6.6", - "@module-federation/sdk": "0.6.6", - "@module-federation/third-party-dts-extractor": "0.6.6", + "@module-federation/managers": "0.6.9", + "@module-federation/sdk": "0.6.9", + "@module-federation/third-party-dts-extractor": "0.6.9", "adm-zip": "^0.5.10", "ansi-colors": "^4.1.3", "axios": "^1.7.4", @@ -5312,20 +5312,20 @@ } }, "node_modules/@module-federation/enhanced": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/@module-federation/enhanced/-/enhanced-0.6.6.tgz", - "integrity": "sha512-gGU1tjaksk5Q5X2zpVb/OmlwvKwVVjTXreuFwkK0Z+9QKM9jbu0B/tPSh6sqibPFeu1yM2HOFlOHJhvFs1PmsA==", + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/@module-federation/enhanced/-/enhanced-0.6.9.tgz", + "integrity": "sha512-4bEGQSE6zJ2FMdBTOrRiVjNNzWhUqzWEJGWbsr0bpLNAl4BVx2ah5MyKTrSYqaW//BRA2qc8rmrIreaIawr3kQ==", "dev": true, "license": "MIT", "dependencies": { - "@module-federation/bridge-react-webpack-plugin": "0.6.6", - "@module-federation/data-prefetch": "0.6.6", - "@module-federation/dts-plugin": "0.6.6", - "@module-federation/managers": "0.6.6", - "@module-federation/manifest": "0.6.6", - "@module-federation/rspack": "0.6.6", - "@module-federation/runtime-tools": "0.6.6", - "@module-federation/sdk": "0.6.6", + "@module-federation/bridge-react-webpack-plugin": "0.6.9", + "@module-federation/data-prefetch": "0.6.9", + "@module-federation/dts-plugin": "0.6.9", + "@module-federation/managers": "0.6.9", + "@module-federation/manifest": "0.6.9", + "@module-federation/rspack": "0.6.9", + "@module-federation/runtime-tools": "0.6.9", + "@module-federation/sdk": "0.6.9", "btoa": "^1.2.1", "upath": "2.0.1" }, @@ -5347,27 +5347,27 @@ } }, "node_modules/@module-federation/managers": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/@module-federation/managers/-/managers-0.6.6.tgz", - "integrity": "sha512-ryj2twbQmo2KhwKn1xYivpaW94l5wfplDU9FwVvW0wc8hC2lJnuGhoiZqXKL7lNaBrZXge3b43Zlgx5OnFfr6A==", + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/@module-federation/managers/-/managers-0.6.9.tgz", + "integrity": "sha512-q3AOQXcWWpdUZI1gDIi9j/UqcP+FJBYXj/e4pNp3QAteJwS/Ve9UP3y0hW27bIbAWZSSajWsYbf/+YLnktA/kQ==", "dev": true, "license": "MIT", "dependencies": { - "@module-federation/sdk": "0.6.6", + "@module-federation/sdk": "0.6.9", "find-pkg": "2.0.0", "fs-extra": "9.1.0" } }, "node_modules/@module-federation/manifest": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/@module-federation/manifest/-/manifest-0.6.6.tgz", - "integrity": "sha512-45ol0fC8RS2d+0iEt5zdp0vctE2CiOfA2kCmOFz79K33occi8sKmyevfSeZGckZy54NiMnLFteIYBsyIa+g7gg==", + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/@module-federation/manifest/-/manifest-0.6.9.tgz", + "integrity": "sha512-JMSPDpHODXOmTyJes8GJ950mbN7tqjQzqgFVUubDOVFOmlC0/MYaRzRPmkApz6d8nUfMbLZYzxNSaBHx8GP0/Q==", "dev": true, "license": "MIT", "dependencies": { - "@module-federation/dts-plugin": "0.6.6", - "@module-federation/managers": "0.6.6", - "@module-federation/sdk": "0.6.6", + "@module-federation/dts-plugin": "0.6.9", + "@module-federation/managers": "0.6.9", + "@module-federation/sdk": "0.6.9", "chalk": "3.0.0", "find-pkg": "2.0.0" } @@ -5426,18 +5426,18 @@ } }, "node_modules/@module-federation/rspack": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/@module-federation/rspack/-/rspack-0.6.6.tgz", - "integrity": "sha512-30X6QPrJ/eCcmUL4GQ06Z9bQwURBnJI0607Fw2ufmAbhDA0PJFtg7NFFfXzsdChms1ACVbgvgfBH8SJg8j3wBg==", + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/@module-federation/rspack/-/rspack-0.6.9.tgz", + "integrity": "sha512-N5yBqN8ijSRZKd0kbIvpZNil0y8rFa8cREKI1QsW1+EYUKwOUBFwF55tFdTmNCKmpZqSEBtcNjRGZXknsYPQxg==", "dev": true, "license": "MIT", "dependencies": { - "@module-federation/bridge-react-webpack-plugin": "0.6.6", - "@module-federation/dts-plugin": "0.6.6", - "@module-federation/managers": "0.6.6", - "@module-federation/manifest": "0.6.6", - "@module-federation/runtime-tools": "0.6.6", - "@module-federation/sdk": "0.6.6" + "@module-federation/bridge-react-webpack-plugin": "0.6.9", + "@module-federation/dts-plugin": "0.6.9", + "@module-federation/managers": "0.6.9", + "@module-federation/manifest": "0.6.9", + "@module-federation/runtime-tools": "0.6.9", + "@module-federation/sdk": "0.6.9" }, "peerDependencies": { "typescript": "^4.9.0 || ^5.0.0", @@ -5453,37 +5453,37 @@ } }, "node_modules/@module-federation/runtime": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/@module-federation/runtime/-/runtime-0.6.6.tgz", - "integrity": "sha512-QsKHUV2HALRzL6mPCdJEZTDuPReKC8MMXf+/VMCtQPp6JhLEjZIO06bfEZqXMbTbTYlMzntIwu1tGCbtJRZDOQ==", + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/@module-federation/runtime/-/runtime-0.6.9.tgz", + "integrity": "sha512-G1x+6jyW5sW1X+TtWaKigGhwqiHE8MESvi3ntE9ICxwELAGBonmsqDqnLSrdEy6poBKslvPANPJr0Nn9pvW9lg==", "dev": true, "license": "MIT", "dependencies": { - "@module-federation/sdk": "0.6.6" + "@module-federation/sdk": "0.6.9" } }, "node_modules/@module-federation/runtime-tools": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/@module-federation/runtime-tools/-/runtime-tools-0.6.6.tgz", - "integrity": "sha512-w2qHa41p6rADWMS1yBjpqNhaLZ4R5oRy9OYGPe6ywjh+8oqbiBl1CfQglcgEBIpHktEjV/upsgsnjHSdJBdeZw==", + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/@module-federation/runtime-tools/-/runtime-tools-0.6.9.tgz", + "integrity": "sha512-AhsEBXo8IW1ATMKS1xfJaxBiHu9n5z6WUOAIWdPpWXXBJhTFgOs0K1xAod0xLJY4YH/B5cwEcHRPN3FEs2/0Ww==", "dev": true, "license": "MIT", "dependencies": { - "@module-federation/runtime": "0.6.6", - "@module-federation/webpack-bundler-runtime": "0.6.6" + "@module-federation/runtime": "0.6.9", + "@module-federation/webpack-bundler-runtime": "0.6.9" } }, "node_modules/@module-federation/sdk": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/@module-federation/sdk/-/sdk-0.6.6.tgz", - "integrity": "sha512-tUv2kPi0FvplcpGi/g4nITAYVAR1RUZ6QvP71T8inmRZSrfcvk1QpGJiL36IjuS67SM3VAoXS0iJ2WX1Rgjvhg==", + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/@module-federation/sdk/-/sdk-0.6.9.tgz", + "integrity": "sha512-xmTxb9LgncxPGsBrN6AT/+aHnFGv8swbeNl0PcSeVbXTGLu3Gp7j+5J+AhJoWNB++SLguRwBd8LjB1d8mNKLDg==", "dev": true, "license": "MIT" }, "node_modules/@module-federation/third-party-dts-extractor": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/@module-federation/third-party-dts-extractor/-/third-party-dts-extractor-0.6.6.tgz", - "integrity": "sha512-xX9p17PpElzATNEulwlJJT731xST7T7OUIDSkkIghp/ICDmZd6WhYJvNBto7xbpaj5SIB7Ocdj4chNGv0xdYPw==", + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/@module-federation/third-party-dts-extractor/-/third-party-dts-extractor-0.6.9.tgz", + "integrity": "sha512-im00IQyX/siJz+SaAmJo6vGmMBig7UYzcrPD1N5NeiZonxdT1RZk9iXUP419UESgovYy4hM6w4qdCq6PMMl2bw==", "dev": true, "license": "MIT", "dependencies": { @@ -5493,14 +5493,14 @@ } }, "node_modules/@module-federation/webpack-bundler-runtime": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/@module-federation/webpack-bundler-runtime/-/webpack-bundler-runtime-0.6.6.tgz", - "integrity": "sha512-0UnY9m1fBgHwTpacYWbht1jB5X4Iqspiu1q8kfjUrv6y+R224//ydUFYYO8xfWx4V9SGQFKlU8XFH0FP/r0Hng==", + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/@module-federation/webpack-bundler-runtime/-/webpack-bundler-runtime-0.6.9.tgz", + "integrity": "sha512-ME1MjNT/a4MFI3HaJDM06olJ+/+H8lk4oDOdwwEZI2JSH3UoqCDrMcjSKCjBNMGzza57AowGobo1LHQeY8yZ8Q==", "dev": true, "license": "MIT", "dependencies": { - "@module-federation/runtime": "0.6.6", - "@module-federation/sdk": "0.6.6" + "@module-federation/runtime": "0.6.9", + "@module-federation/sdk": "0.6.9" } }, "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { @@ -6429,19 +6429,19 @@ } }, "node_modules/@nx/angular": { - "version": "20.0.6", - "resolved": "https://registry.npmjs.org/@nx/angular/-/angular-20.0.6.tgz", - "integrity": "sha512-0UfCEp4JeQEYMpUjaipHEH/V/GRHGCd+vgPN9EdhpkSqw2YyuBXlZiX1q0DgzMxZRRBRTB+p37FgRPu32lOI6g==", + "version": "20.1.2", + "resolved": "https://registry.npmjs.org/@nx/angular/-/angular-20.1.2.tgz", + "integrity": "sha512-brGmrT6DDPCV7liN69N5T0rHqkn2FO7zceAum++h/s65++g4CscZTIS0CyKr8ZBRG9wvDBuOWkKbnwtV9297HA==", "dev": true, "license": "MIT", "dependencies": { - "@module-federation/enhanced": "0.6.6", - "@nx/devkit": "20.0.6", - "@nx/eslint": "20.0.6", - "@nx/js": "20.0.6", - "@nx/web": "20.0.6", - "@nx/webpack": "20.0.6", - "@nx/workspace": "20.0.6", + "@module-federation/enhanced": "0.6.9", + "@nx/devkit": "20.1.2", + "@nx/eslint": "20.1.2", + "@nx/js": "20.1.2", + "@nx/web": "20.1.2", + "@nx/webpack": "20.1.2", + "@nx/workspace": "20.1.2", "@phenomnomnominal/tsquery": "~5.0.1", "@typescript-eslint/type-utils": "^8.0.0", "chalk": "^4.1.0", @@ -6534,15 +6534,15 @@ } }, "node_modules/@nx/cypress": { - "version": "20.0.6", - "resolved": "https://registry.npmjs.org/@nx/cypress/-/cypress-20.0.6.tgz", - "integrity": "sha512-b26Ucgf+dAdTRlBGhFi8Xjeqw1mbUrxn3nwAOYNwuivc+CZCeokba5/orldNAlBlJKvHe0QmSAI3wpDjdU05Ww==", + "version": "20.1.2", + "resolved": "https://registry.npmjs.org/@nx/cypress/-/cypress-20.1.2.tgz", + "integrity": "sha512-kT/vXWqD4DxYawtVBA3E1EYlFi6ba6XvEnh+Ac5A1EX0PmVqBxhtBxpDlLjJxDOEgpIWbZDFdkJ41twYQgYDGA==", "dev": true, "license": "MIT", "dependencies": { - "@nx/devkit": "20.0.6", - "@nx/eslint": "20.0.6", - "@nx/js": "20.0.6", + "@nx/devkit": "20.1.2", + "@nx/eslint": "20.1.2", + "@nx/js": "20.1.2", "@phenomnomnominal/tsquery": "~5.0.1", "detect-port": "^1.5.1", "tslib": "^2.3.0" @@ -6557,9 +6557,9 @@ } }, "node_modules/@nx/devkit": { - "version": "20.0.6", - "resolved": "https://registry.npmjs.org/@nx/devkit/-/devkit-20.0.6.tgz", - "integrity": "sha512-vUjVVEJgfq/roCzDDZDXduwnhVXl1MM5No2UELUka2oNBK09pPigdFxzUNh8XvmOyFskCGDTLKH/dAO5yTD5Bg==", + "version": "20.1.2", + "resolved": "https://registry.npmjs.org/@nx/devkit/-/devkit-20.1.2.tgz", + "integrity": "sha512-MTEWiEST7DhzZ2QmrixLnHfYVDZk7QN9omLL8m+5Etcn/3ZKa1aAo9Amd2MkUM+0MPoTKnxoGdw0fQUpAy21Mg==", "dev": true, "license": "MIT", "dependencies": { @@ -6577,14 +6577,14 @@ } }, "node_modules/@nx/eslint": { - "version": "20.0.6", - "resolved": "https://registry.npmjs.org/@nx/eslint/-/eslint-20.0.6.tgz", - "integrity": "sha512-07Ign5GQXZif6zHDR2oB4wkf2amSvoGhYWJ17fmqDsMF/nWYOohL+DbjAaqDORXWXL1bnmRBaj/lAkDNsmW3QA==", + "version": "20.1.2", + "resolved": "https://registry.npmjs.org/@nx/eslint/-/eslint-20.1.2.tgz", + "integrity": "sha512-VMJ65E0jUEjup8hxz6LtqYbYnk2TUoLCM7ZV4rZdPqm0rLvlHDmb7BfdY2u2sZa3dwRDtupeDMlbyPX/Eb8Rcw==", "dev": true, "license": "MIT", "dependencies": { - "@nx/devkit": "20.0.6", - "@nx/js": "20.0.6", + "@nx/devkit": "20.1.2", + "@nx/js": "20.1.2", "semver": "^7.5.3", "tslib": "^2.3.0", "typescript": "~5.4.2" @@ -6600,14 +6600,14 @@ } }, "node_modules/@nx/eslint-plugin": { - "version": "20.0.6", - "resolved": "https://registry.npmjs.org/@nx/eslint-plugin/-/eslint-plugin-20.0.6.tgz", - "integrity": "sha512-wFWg9X4dhRVY5pIAuqXLKTQSL3FzWHbV5kpg7S+y2X3jFg3pezqa8EDBkAcerSk7rour1G2hlXAfHX/W3HCrBQ==", + "version": "20.1.2", + "resolved": "https://registry.npmjs.org/@nx/eslint-plugin/-/eslint-plugin-20.1.2.tgz", + "integrity": "sha512-eLOVzaBPwS71Bb07jhJFZYtkvD33fZb3ObwLDXG5DmfpNpYBGOD4XX0qj6eq/5cfsIck6n8n7RKVm+7ZyqYowg==", "dev": true, "license": "MIT", "dependencies": { - "@nx/devkit": "20.0.6", - "@nx/js": "20.0.6", + "@nx/devkit": "20.1.2", + "@nx/js": "20.1.2", "@typescript-eslint/type-utils": "^8.0.0", "@typescript-eslint/utils": "^8.0.0", "chalk": "^4.1.0", @@ -6711,16 +6711,16 @@ } }, "node_modules/@nx/jest": { - "version": "20.0.6", - "resolved": "https://registry.npmjs.org/@nx/jest/-/jest-20.0.6.tgz", - "integrity": "sha512-/v9NavOOWcUpzgbjfYip0zipneJPhKUQd5rU3bTr0CqCJw0I+YQXotToUkzzMQYT6zmNrq7ySTMH1N8rXdy7NQ==", + "version": "20.1.2", + "resolved": "https://registry.npmjs.org/@nx/jest/-/jest-20.1.2.tgz", + "integrity": "sha512-KUHm+NcH4Iq/Pk6GpaRhACEHd8Gt28dbXUAErxo/T9b+a3ir/6uUb4Sr+aXf63uYSePDhUmYbrYxGf/KzS2I8w==", "dev": true, "license": "MIT", "dependencies": { "@jest/reporters": "^29.4.1", "@jest/test-result": "^29.4.1", - "@nx/devkit": "20.0.6", - "@nx/js": "20.0.6", + "@nx/devkit": "20.1.2", + "@nx/js": "20.1.2", "@phenomnomnominal/tsquery": "~5.0.1", "chalk": "^4.1.0", "identity-obj-proxy": "3.0.0", @@ -6791,9 +6791,9 @@ } }, "node_modules/@nx/js": { - "version": "20.0.6", - "resolved": "https://registry.npmjs.org/@nx/js/-/js-20.0.6.tgz", - "integrity": "sha512-/bAMtcgKX1Te3yCzbbv+QQLnFwb6SxE0iCc6EzxiLepmGhnd0iOArUqepB1mVipfeaO37n00suFjFv1xsaqLHg==", + "version": "20.1.2", + "resolved": "https://registry.npmjs.org/@nx/js/-/js-20.1.2.tgz", + "integrity": "sha512-+ULLy0vuAUyRicQqjMsG3JmgEylZdciJJOuOanwrmmG/+jv64nUJYycZbwPmGsioViHk/0WB1d5SWWfH7cZ+Ww==", "dev": true, "license": "MIT", "dependencies": { @@ -6804,8 +6804,8 @@ "@babel/preset-env": "^7.23.2", "@babel/preset-typescript": "^7.22.5", "@babel/runtime": "^7.22.6", - "@nx/devkit": "20.0.6", - "@nx/workspace": "20.0.6", + "@nx/devkit": "20.1.2", + "@nx/workspace": "20.1.2", "@zkochan/js-yaml": "0.0.7", "babel-plugin-const-enum": "^1.0.1", "babel-plugin-macros": "^2.8.0", @@ -7079,17 +7079,17 @@ } }, "node_modules/@nx/nest": { - "version": "20.0.6", - "resolved": "https://registry.npmjs.org/@nx/nest/-/nest-20.0.6.tgz", - "integrity": "sha512-vACNZ+jTURJfVIpLQURgbR12O7mOqoVjCSfqbXBIC9pc4kYqShPW0SnAybwxKR+zpWGeO1nel7VXEH95HgAXuA==", + "version": "20.1.2", + "resolved": "https://registry.npmjs.org/@nx/nest/-/nest-20.1.2.tgz", + "integrity": "sha512-pNQTI30KMnvCh39aBIX7LFY1hgolN4xH7K2Oc/49Hm87cbySg18nYHnTnotz0Yyy3CR5Q6/smL0uZXfBPJ3WTw==", "dev": true, "license": "MIT", "dependencies": { "@nestjs/schematics": "^9.1.0", - "@nx/devkit": "20.0.6", - "@nx/eslint": "20.0.6", - "@nx/js": "20.0.6", - "@nx/node": "20.0.6", + "@nx/devkit": "20.1.2", + "@nx/eslint": "20.1.2", + "@nx/js": "20.1.2", + "@nx/node": "20.1.2", "tslib": "^2.3.0" } }, @@ -7221,23 +7221,23 @@ } }, "node_modules/@nx/node": { - "version": "20.0.6", - "resolved": "https://registry.npmjs.org/@nx/node/-/node-20.0.6.tgz", - "integrity": "sha512-/6khofVKgpdglkSE6XDz9tk4kCeEXQaIPOH1PgWqY25hoim/VSXjZ1XMVmPvnvd7m2lsFLDrqZlwIGWTrT2cFw==", + "version": "20.1.2", + "resolved": "https://registry.npmjs.org/@nx/node/-/node-20.1.2.tgz", + "integrity": "sha512-PGPSXkzTJc97GnsRNSBcekH5L5BM/SCSWA8lH/bBV/N8HBFUWppsv0Nj+UUcGGH3O3kjEMrhtbG9iJijX7+9kw==", "dev": true, "license": "MIT", "dependencies": { - "@nx/devkit": "20.0.6", - "@nx/eslint": "20.0.6", - "@nx/jest": "20.0.6", - "@nx/js": "20.0.6", + "@nx/devkit": "20.1.2", + "@nx/eslint": "20.1.2", + "@nx/jest": "20.1.2", + "@nx/js": "20.1.2", "tslib": "^2.3.0" } }, "node_modules/@nx/nx-darwin-arm64": { - "version": "20.0.6", - "resolved": "https://registry.npmjs.org/@nx/nx-darwin-arm64/-/nx-darwin-arm64-20.0.6.tgz", - "integrity": "sha512-SUVfEqzl/iy2NzTbpY2E9lHSxs8c9QERhTILp5OOt0Vgmhn9iTxVEIoSCjzz/MyX066eARarUymUyK4JCg3mqw==", + "version": "20.1.2", + "resolved": "https://registry.npmjs.org/@nx/nx-darwin-arm64/-/nx-darwin-arm64-20.1.2.tgz", + "integrity": "sha512-PJ91TQhd28kitDBubKUOXMYvrtSDrG+rr8MsIe9cHo1CvU9smcGVBwuHBxniq0DXsyOX/5GL6ngq7hjN2nQ3XQ==", "cpu": [ "arm64" ], @@ -7252,9 +7252,9 @@ } }, "node_modules/@nx/nx-darwin-x64": { - "version": "20.0.6", - "resolved": "https://registry.npmjs.org/@nx/nx-darwin-x64/-/nx-darwin-x64-20.0.6.tgz", - "integrity": "sha512-JI0kcJGBeIj3sb+kC0nZMOSXFnvCOtGbAVK3HHJ9DSRxckLq5bImwqdfYSNJL9ocU8YU+Qds/SercEV02gQOkQ==", + "version": "20.1.2", + "resolved": "https://registry.npmjs.org/@nx/nx-darwin-x64/-/nx-darwin-x64-20.1.2.tgz", + "integrity": "sha512-1fopau7nxIhTF26vDTIzMxl15AtW4FvUSdy+r1mNRKrKyjjpqnlu00SQBW7JzGV0agDD1B/61yYei5Q2aMOt7Q==", "cpu": [ "x64" ], @@ -7269,9 +7269,9 @@ } }, "node_modules/@nx/nx-freebsd-x64": { - "version": "20.0.6", - "resolved": "https://registry.npmjs.org/@nx/nx-freebsd-x64/-/nx-freebsd-x64-20.0.6.tgz", - "integrity": "sha512-om9Sh5Pg5aRDlBWyHMAX/1swLSj2pCqk1grXN6RcJ8O3tXLI35fj4wz6sPDRASwC1xuHwET2DG/20Ec6n1Ko3A==", + "version": "20.1.2", + "resolved": "https://registry.npmjs.org/@nx/nx-freebsd-x64/-/nx-freebsd-x64-20.1.2.tgz", + "integrity": "sha512-55YgIp3v4zz7xMzJO93dtglbOTER2XdS6jrCt8GbKaWGFl5drRrBoNGONtiGNU7C3hLx1VsorbynCkJT18PjKQ==", "cpu": [ "x64" ], @@ -7286,9 +7286,9 @@ } }, "node_modules/@nx/nx-linux-arm-gnueabihf": { - "version": "20.0.6", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-20.0.6.tgz", - "integrity": "sha512-XIomXUqnH3w1aqRu0T+Wcn9roXT1bG1PjuX+bmGLkSiZ+ZyY/zYfhg6WKbox3TqQcdC1jNUkzEQlLGcfWaGc6w==", + "version": "20.1.2", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-20.1.2.tgz", + "integrity": "sha512-sMhNA8uAV43UYVEXEa8TZ8Fjpom4CGq1umTptEGOF4TTtdNn2AUBreg+0bVODM8MMSzRWGI1VbkZzHESnAPwqw==", "cpu": [ "arm" ], @@ -7303,9 +7303,9 @@ } }, "node_modules/@nx/nx-linux-arm64-gnu": { - "version": "20.0.6", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-20.0.6.tgz", - "integrity": "sha512-Asx2F+WtauELssmrQf1y4ZeiMIsgbL/+PnD+WgbvHVWbl7cRUfLJqEhOR5fQG6CiNTIXvOyzXMoaJVA9hTub+Q==", + "version": "20.1.2", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-20.1.2.tgz", + "integrity": "sha512-bsevarNHglaYLmIvPNQOdHrBnBgaW3EOUM0flwaXdWuZbL1bWx8GoVwHp9yJpZOAOfIF/Nhq5iTpaZB2nYFrAA==", "cpu": [ "arm64" ], @@ -7320,9 +7320,9 @@ } }, "node_modules/@nx/nx-linux-arm64-musl": { - "version": "20.0.6", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-20.0.6.tgz", - "integrity": "sha512-4lyBaLWSv7VNMOXWxtuDNiSOE4M5QGiVHimSvQ9PBwgnrvEuc6fCv/Nc8ecU0rINHRQJruYMTD/kKBCsahwJUQ==", + "version": "20.1.2", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-20.1.2.tgz", + "integrity": "sha512-GFZTptkhZPL/iZ3tYDmspIcPEaXyy/L/o59gyp33GoFAAyDhiXIF7J1Lz81Xn8VKrX6TvEY8/9qSh86pb7qzDQ==", "cpu": [ "arm64" ], @@ -7337,9 +7337,9 @@ } }, "node_modules/@nx/nx-linux-x64-gnu": { - "version": "20.0.6", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-20.0.6.tgz", - "integrity": "sha512-HGZzX7un/rJvADKwN27HM0e3Gx19hSndCoqZUtqHgrFRdUvTfHTWNpT6uZ5XW/5bNnRKdUinY9DHhlYpE0u4KQ==", + "version": "20.1.2", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-20.1.2.tgz", + "integrity": "sha512-yqEW/iglKT4d9lgfnwSNhmDzPxCkRhtdmZqOYpGDM0eZFwYwJF+WRGjW8xIqMj8PA1yrGItzXZOmyFjJqHAF2w==", "cpu": [ "x64" ], @@ -7354,9 +7354,9 @@ } }, "node_modules/@nx/nx-linux-x64-musl": { - "version": "20.0.6", - "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-20.0.6.tgz", - "integrity": "sha512-OwMq+ozzCOCtAViOouHbe/MXqep/q4EKg44YelUqVNIe/2XimcIfMlBQFk1DOcmibesxa3yWMKAdg2IGUnG+pQ==", + "version": "20.1.2", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-20.1.2.tgz", + "integrity": "sha512-SP6PpWT4cQVrC4WJQdpfADrYJQzkbhgmcGleWbpr7II1HJgOsAcvoDwQGpPQX+3Wo+VBiNecvUAOzacMQkXPGw==", "cpu": [ "x64" ], @@ -7371,9 +7371,9 @@ } }, "node_modules/@nx/nx-win32-arm64-msvc": { - "version": "20.0.6", - "resolved": "https://registry.npmjs.org/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-20.0.6.tgz", - "integrity": "sha512-2D8TIjyi5dJLy4cx8u7YKunW6+EG9FAuBUo75qMCozTBw1EPTK2lzwLE2d8C7WOxBA148O2wzD5uiX1vCt2Tzg==", + "version": "20.1.2", + "resolved": "https://registry.npmjs.org/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-20.1.2.tgz", + "integrity": "sha512-JZQx9gr39LY3D7uleiXlpxUsavuOrOQNBocwKHkAMnykaT/e1VCxTnm/hk+2b4foWwfURTqoRiFEba70iiCdYg==", "cpu": [ "arm64" ], @@ -7388,9 +7388,9 @@ } }, "node_modules/@nx/nx-win32-x64-msvc": { - "version": "20.0.6", - "resolved": "https://registry.npmjs.org/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-20.0.6.tgz", - "integrity": "sha512-B83kpN1+KdJ97P0Rw/KRyZ5fZPtKimvwg/TAJdWR1D8oqdrpaZwgTd9dcsTNavvynUsPqM3GdjmFKzTYTZ4MFQ==", + "version": "20.1.2", + "resolved": "https://registry.npmjs.org/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-20.1.2.tgz", + "integrity": "sha512-6GmT8iswDiCvJaCtW9DpWeAQmLS/kfAuRLYBisfzlONuLPaDdjhgVIxZBqqUSFfclwcVz+NhIOGvdr0aGFZCtQ==", "cpu": [ "x64" ], @@ -7405,30 +7405,30 @@ } }, "node_modules/@nx/storybook": { - "version": "20.0.6", - "resolved": "https://registry.npmjs.org/@nx/storybook/-/storybook-20.0.6.tgz", - "integrity": "sha512-eqQKs67bRb9vutCt+dcR5CUhnSiQ2X82cYNryHEu/u8qE0LRfmCxxWh1DUNGxz1v1SYquo6RBo0qORm8oef3Pg==", + "version": "20.1.2", + "resolved": "https://registry.npmjs.org/@nx/storybook/-/storybook-20.1.2.tgz", + "integrity": "sha512-M3ymcFuMYgZ2GT6hPjvVbtSCyfVPGmDy7DY1oHOYBkLqywkjzTcpjmN6Kqm5ZQUZfKYFWgIkNs2J5VL9Knn3cg==", "dev": true, "license": "MIT", "dependencies": { - "@nx/cypress": "20.0.6", - "@nx/devkit": "20.0.6", - "@nx/eslint": "20.0.6", - "@nx/js": "20.0.6", + "@nx/cypress": "20.1.2", + "@nx/devkit": "20.1.2", + "@nx/eslint": "20.1.2", + "@nx/js": "20.1.2", "@phenomnomnominal/tsquery": "~5.0.1", "semver": "^7.5.3", "tslib": "^2.3.0" } }, "node_modules/@nx/web": { - "version": "20.0.6", - "resolved": "https://registry.npmjs.org/@nx/web/-/web-20.0.6.tgz", - "integrity": "sha512-lYu9FddREZYbjbjS9YYnXu+uGQUB6MptNvPNSvYRRUcdq7c8Kh10P21YyK2Ox7FsEUeqly+XVvhlKNXeQF5anw==", + "version": "20.1.2", + "resolved": "https://registry.npmjs.org/@nx/web/-/web-20.1.2.tgz", + "integrity": "sha512-CRMAJXwj375J+/GI9hRfOt2SJ0DQ5prCzOcmXJvQIfHy3CT5chrkSj2qc7IgKkkMiqZojr4VCTUHmJ2WAR3sCw==", "dev": true, "license": "MIT", "dependencies": { - "@nx/devkit": "20.0.6", - "@nx/js": "20.0.6", + "@nx/devkit": "20.1.2", + "@nx/js": "20.1.2", "detect-port": "^1.5.1", "http-server": "^14.1.0", "picocolors": "^1.1.0", @@ -7436,35 +7436,35 @@ } }, "node_modules/@nx/webpack": { - "version": "20.0.6", - "resolved": "https://registry.npmjs.org/@nx/webpack/-/webpack-20.0.6.tgz", - "integrity": "sha512-LvjkJ0yVXDCNgxxIKYLMtEJVVdvBVHcB9mgwPdBfl38STAf/HwTuB7XXTZVYu+m9iPusU1VpFpaUlbpQN79f8A==", + "version": "20.1.2", + "resolved": "https://registry.npmjs.org/@nx/webpack/-/webpack-20.1.2.tgz", + "integrity": "sha512-H67DkdpaGnUwYbz4u31+2/TSRmkvBQHX742FNKJAc1/D0uzHH6GI3am0h0QF9wrJyc/fXGVNfRZLEh9ScU70Jw==", "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.23.2", "@module-federation/enhanced": "^0.6.0", "@module-federation/sdk": "^0.6.0", - "@nx/devkit": "20.0.6", - "@nx/js": "20.0.6", + "@nx/devkit": "20.1.2", + "@nx/js": "20.1.2", "@phenomnomnominal/tsquery": "~5.0.1", "ajv": "^8.12.0", "autoprefixer": "^10.4.9", "babel-loader": "^9.1.2", "browserslist": "^4.21.4", - "chalk": "^4.1.0", "copy-webpack-plugin": "^10.2.4", "css-loader": "^6.4.0", "css-minimizer-webpack-plugin": "^5.0.0", "express": "^4.19.2", "fork-ts-checker-webpack-plugin": "7.2.13", - "http-proxy-middleware": "^3.0.0", + "http-proxy-middleware": "^3.0.3", "less": "4.1.3", "less-loader": "11.1.0", "license-webpack-plugin": "^4.0.2", "loader-utils": "^2.0.3", "mini-css-extract-plugin": "~2.4.7", "parse5": "4.0.0", + "picocolors": "^1.1.0", "postcss": "^8.4.38", "postcss-import": "~14.1.0", "postcss-loader": "^6.1.1", @@ -7473,7 +7473,7 @@ "sass-loader": "^12.2.0", "source-map-loader": "^5.0.0", "style-loader": "^3.3.0", - "stylus": "^0.59.0", + "stylus": "^0.64.0", "stylus-loader": "^7.1.0", "terser-webpack-plugin": "^5.3.3", "ts-loader": "^9.3.1", @@ -7485,22 +7485,6 @@ "webpack-subresource-integrity": "^5.1.0" } }, - "node_modules/@nx/webpack/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/@nx/webpack/node_modules/array-union": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/array-union/-/array-union-3.0.1.tgz", @@ -7549,23 +7533,23 @@ "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/@nx/webpack/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@nx/webpack/node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "ms": "2.0.0" } }, + "node_modules/@nx/webpack/node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, "node_modules/@nx/webpack/node_modules/cookie": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", @@ -7654,16 +7638,6 @@ } } }, - "node_modules/@nx/webpack/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, "node_modules/@nx/webpack/node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -7717,6 +7691,23 @@ "node": ">= 0.10.0" } }, + "node_modules/@nx/webpack/node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/@nx/webpack/node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, "node_modules/@nx/webpack/node_modules/finalhandler": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", @@ -7736,6 +7727,23 @@ "node": ">= 0.8" } }, + "node_modules/@nx/webpack/node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/@nx/webpack/node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, "node_modules/@nx/webpack/node_modules/globby": { "version": "12.2.0", "resolved": "https://registry.npmjs.org/globby/-/globby-12.2.0.tgz", @@ -7757,14 +7765,32 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@nx/webpack/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/@nx/webpack/node_modules/http-proxy-middleware": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.3.tgz", + "integrity": "sha512-usY0HG5nyDUwtqpiZdETNbmKtw3QQ1jwYFZ9wi5iHzX2BcILwQKtYDJPo7XHTsu5Z0B2Hj3W9NNnbd+AjFWjqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-proxy": "^1.17.15", + "debug": "^4.3.6", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.3", + "is-plain-object": "^5.0.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@nx/webpack/node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, "node_modules/@nx/webpack/node_modules/less": { @@ -7887,9 +7913,9 @@ } }, "node_modules/@nx/webpack/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, "license": "MIT" }, @@ -8031,6 +8057,23 @@ "node": ">= 0.8.0" } }, + "node_modules/@nx/webpack/node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/@nx/webpack/node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, "node_modules/@nx/webpack/node_modules/send/node_modules/encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -8041,13 +8084,6 @@ "node": ">= 0.8" } }, - "node_modules/@nx/webpack/node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, "node_modules/@nx/webpack/node_modules/serve-static": { "version": "1.16.2", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", @@ -8088,30 +8124,17 @@ "node": ">=0.10.0" } }, - "node_modules/@nx/webpack/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@nx/workspace": { - "version": "20.0.6", - "resolved": "https://registry.npmjs.org/@nx/workspace/-/workspace-20.0.6.tgz", - "integrity": "sha512-A7lle47I4JggbhXoUVvkuvULqF0Xejy4LpE0txz9OIM5a9HxW1aIHYYQFuROBuVlMFxAJusPeYFJCCvb+qBxKw==", + "version": "20.1.2", + "resolved": "https://registry.npmjs.org/@nx/workspace/-/workspace-20.1.2.tgz", + "integrity": "sha512-YZiBwHU+NsJvJ7e7AZnyk5cP523AIHmHFf28nEpBY3zhxLghx/s9C99Swbw+uUyWlUf7JtTO9jB6OsEfMc38Uw==", "dev": true, "license": "MIT", "dependencies": { - "@nx/devkit": "20.0.6", + "@nx/devkit": "20.1.2", "chalk": "^4.1.0", "enquirer": "~2.3.6", - "nx": "20.0.6", + "nx": "20.1.2", "tslib": "^2.3.0", "yargs-parser": "21.1.1" } @@ -12150,9 +12173,9 @@ "license": "BSD-2-Clause" }, "node_modules/@yarnpkg/parsers": { - "version": "3.0.0-rc.46", - "resolved": "https://registry.npmjs.org/@yarnpkg/parsers/-/parsers-3.0.0-rc.46.tgz", - "integrity": "sha512-aiATs7pSutzda/rq8fnuPwTglyVwjM22bNnK2ZgjrpAjQHSSl3lztd2f9evst1W/qnC58DRz7T7QndUDumAR4Q==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@yarnpkg/parsers/-/parsers-3.0.2.tgz", + "integrity": "sha512-/HcYgtUSiJiot/XWGLOlGxPYUG65+/31V8oqk17vZLW1xlCoR4PampyePljOxY2n8/3jz9+tIFzICsyGujJZoA==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -12160,7 +12183,7 @@ "tslib": "^2.4.0" }, "engines": { - "node": ">=14.15.0" + "node": ">=18.12.0" } }, "node_modules/@zkochan/js-yaml": { @@ -27369,16 +27392,16 @@ "license": "MIT" }, "node_modules/nx": { - "version": "20.0.6", - "resolved": "https://registry.npmjs.org/nx/-/nx-20.0.6.tgz", - "integrity": "sha512-z8PMPEXxtADwxsNXamZdDbx65fcNcR4gTmX7N94GKmpZNrjwd3m7RcnoYgQp5vA8kFQkMR+320mtq5NkGJPZvg==", + "version": "20.1.2", + "resolved": "https://registry.npmjs.org/nx/-/nx-20.1.2.tgz", + "integrity": "sha512-CvjmuQmI0RWLYZxRSIgQZmzsQv6dPp9oI0YZE3L1dagBPfTf5Cun65I0GLt7bdkDnVx2PGYkDbIoJSv2/V+83Q==", "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { "@napi-rs/wasm-runtime": "0.2.4", "@yarnpkg/lockfile": "^1.1.0", - "@yarnpkg/parsers": "3.0.0-rc.46", + "@yarnpkg/parsers": "3.0.2", "@zkochan/js-yaml": "0.0.7", "axios": "^1.7.4", "chalk": "^4.1.0", @@ -27414,16 +27437,16 @@ "nx-cloud": "bin/nx-cloud.js" }, "optionalDependencies": { - "@nx/nx-darwin-arm64": "20.0.6", - "@nx/nx-darwin-x64": "20.0.6", - "@nx/nx-freebsd-x64": "20.0.6", - "@nx/nx-linux-arm-gnueabihf": "20.0.6", - "@nx/nx-linux-arm64-gnu": "20.0.6", - "@nx/nx-linux-arm64-musl": "20.0.6", - "@nx/nx-linux-x64-gnu": "20.0.6", - "@nx/nx-linux-x64-musl": "20.0.6", - "@nx/nx-win32-arm64-msvc": "20.0.6", - "@nx/nx-win32-x64-msvc": "20.0.6" + "@nx/nx-darwin-arm64": "20.1.2", + "@nx/nx-darwin-x64": "20.1.2", + "@nx/nx-freebsd-x64": "20.1.2", + "@nx/nx-linux-arm-gnueabihf": "20.1.2", + "@nx/nx-linux-arm64-gnu": "20.1.2", + "@nx/nx-linux-arm64-musl": "20.1.2", + "@nx/nx-linux-x64-gnu": "20.1.2", + "@nx/nx-linux-x64-musl": "20.1.2", + "@nx/nx-win32-arm64-msvc": "20.1.2", + "@nx/nx-win32-x64-msvc": "20.1.2" }, "peerDependencies": { "@swc-node/register": "^1.8.0", @@ -27508,13 +27531,13 @@ } }, "node_modules/nx/node_modules/dotenv-expand": { - "version": "11.0.6", - "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.6.tgz", - "integrity": "sha512-8NHi73otpWsZGBSZwwknTXS5pqMOrk9+Ssrna8xCaxkzEpU9OTf9R5ArQGVw03//Zmk9MOwLPng9WwndvpAJ5g==", + "version": "11.0.7", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.7.tgz", + "integrity": "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "dotenv": "^16.4.4" + "dotenv": "^16.4.5" }, "engines": { "node": ">=12" @@ -29640,9 +29663,9 @@ } }, "node_modules/rambda": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/rambda/-/rambda-9.3.0.tgz", - "integrity": "sha512-cl/7DCCKNxmsbc0dXZTJTY08rvDdzLhVfE6kPBson1fWzDapLzv0RKSzjpmAqP53fkQqAvq05gpUVHTrUNsuxg==", + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/rambda/-/rambda-9.4.0.tgz", + "integrity": "sha512-B7y7goUd+g0hNl5ODGUejNNERQL5gD8uANJ5Y5aHly8v0jKesFlwIe7prPfuJxttDpe3otQzHJ4NXMpTmL9ELA==", "dev": true, "license": "MIT" }, @@ -31952,23 +31975,23 @@ "optional": true }, "node_modules/stylus": { - "version": "0.59.0", - "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.59.0.tgz", - "integrity": "sha512-lQ9w/XIOH5ZHVNuNbWW8D822r+/wBSO/d6XvtyHLF7LW4KaCIDeVbvn5DF8fGCJAUCwVhVi/h6J0NUcnylUEjg==", + "version": "0.64.0", + "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.64.0.tgz", + "integrity": "sha512-ZIdT8eUv8tegmqy1tTIdJv9We2DumkNZFdCF5mz/Kpq3OcTaxSuCAYZge6HKK2CmNC02G1eJig2RV7XTw5hQrA==", "devOptional": true, "license": "MIT", "dependencies": { - "@adobe/css-tools": "^4.0.1", + "@adobe/css-tools": "~4.3.3", "debug": "^4.3.2", - "glob": "^7.1.6", - "sax": "~1.2.4", + "glob": "^10.4.5", + "sax": "~1.4.1", "source-map": "^0.7.3" }, "bin": { "stylus": "bin/stylus" }, "engines": { - "node": "*" + "node": ">=16" }, "funding": { "url": "https://opencollective.com/stylus" @@ -31996,12 +32019,49 @@ "webpack": "^5.0.0" } }, - "node_modules/stylus/node_modules/sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "node_modules/stylus/node_modules/@adobe/css-tools": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.3.tgz", + "integrity": "sha512-rE0Pygv0sEZ4vBWHlAgJLGDU7Pm8xoO6p3wsEceb7GYAjScrOHpEo8KK/eVkAcnSM+slAEtXjA2JpdjLp4fJQQ==", "devOptional": true, - "license": "ISC" + "license": "MIT" + }, + "node_modules/stylus/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/stylus/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, "node_modules/supports-color": { "version": "5.5.0", diff --git a/package.json b/package.json index 8719f2a7..1be0035b 100644 --- a/package.json +++ b/package.json @@ -153,16 +153,16 @@ "@angular/pwa": "18.2.9", "@nestjs/schematics": "10.0.1", "@nestjs/testing": "10.1.3", - "@nx/angular": "20.0.6", - "@nx/cypress": "20.0.6", - "@nx/eslint-plugin": "20.0.6", - "@nx/jest": "20.0.6", - "@nx/js": "20.0.6", - "@nx/nest": "20.0.6", - "@nx/node": "20.0.6", - "@nx/storybook": "20.0.6", - "@nx/web": "20.0.6", - "@nx/workspace": "20.0.6", + "@nx/angular": "20.1.2", + "@nx/cypress": "20.1.2", + "@nx/eslint-plugin": "20.1.2", + "@nx/jest": "20.1.2", + "@nx/js": "20.1.2", + "@nx/nest": "20.1.2", + "@nx/node": "20.1.2", + "@nx/storybook": "20.1.2", + "@nx/web": "20.1.2", + "@nx/workspace": "20.1.2", "@schematics/angular": "18.2.9", "@simplewebauthn/types": "9.0.1", "@storybook/addon-essentials": "8.3.6", @@ -193,7 +193,7 @@ "jest": "29.7.0", "jest-environment-jsdom": "29.7.0", "jest-preset-angular": "14.1.0", - "nx": "20.0.6", + "nx": "20.1.2", "prettier": "3.3.3", "prettier-plugin-organize-attributes": "1.0.0", "prisma": "5.22.0", From c9693d2d58f0f7aaef9e22658db80aa899f3b1be Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Tue, 19 Nov 2024 14:01:38 +0100 Subject: [PATCH 40/65] Feature/upgrade countries and timezones to version 3.7.2 (#4021) * Upgrade countries-and-timezones to version 3.7.2 * Update changelog --- CHANGELOG.md | 1 + package-lock.json | 8 ++++---- package.json | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d6e5eb2..c87a00cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Upgraded `countries-and-timezones` from version `3.4.1` to `3.7.2` - Upgraded `Nx` from version `20.0.6` to `20.1.2` ## 2.123.0 - 2024-11-16 diff --git a/package-lock.json b/package-lock.json index e51ca278..7e1d1cd9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -60,7 +60,7 @@ "class-transformer": "0.5.1", "class-validator": "0.14.1", "color": "4.2.3", - "countries-and-timezones": "3.4.1", + "countries-and-timezones": "3.7.2", "countries-list": "3.1.1", "countup.js": "2.8.0", "date-fns": "3.6.0", @@ -15270,9 +15270,9 @@ } }, "node_modules/countries-and-timezones": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/countries-and-timezones/-/countries-and-timezones-3.4.1.tgz", - "integrity": "sha512-INeHGCony4XUUR8iGL/lmt9s1Oi+n+gFHeJAMfbV5hJfYeDOB8JG1oxz5xFQu5oBZoRCJe/87k1Vzue9DoIauA==", + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/countries-and-timezones/-/countries-and-timezones-3.7.2.tgz", + "integrity": "sha512-BHAMt4pKb3U3r/mRfiIlVnDhRd8m6VC20gwCWtpZGZkSsjZmnMDKFnnjWYGWhBmypQAqcQILFJwmEhIgWGVTmw==", "license": "MIT", "engines": { "node": ">=8.x", diff --git a/package.json b/package.json index 1be0035b..c2a6995a 100644 --- a/package.json +++ b/package.json @@ -106,7 +106,7 @@ "class-transformer": "0.5.1", "class-validator": "0.14.1", "color": "4.2.3", - "countries-and-timezones": "3.4.1", + "countries-and-timezones": "3.7.2", "countries-list": "3.1.1", "countup.js": "2.8.0", "date-fns": "3.6.0", From 6d440eb777c14d40199171853391d6ca75575c80 Mon Sep 17 00:00:00 2001 From: QURBAN AHMAD <79472606+qur786@users.noreply.github.com> Date: Thu, 21 Nov 2024 00:50:11 +0530 Subject: [PATCH 41/65] Feature/add count to admin user endpoint response (#4058) * Add count to admin user endpoint response * Update changelog --- CHANGELOG.md | 1 + apps/api/src/app/admin/admin.service.ts | 25 +++++++++++++++++-- apps/client/src/app/services/admin.service.ts | 2 +- .../lib/interfaces/admin-users.interface.ts | 1 + 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c87a00cd..3bad82b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added pagination parameters (`skip`, `take`) to the endpoint `GET api/v1/admin/user` +- Added pagination response (`count`) to the endpoint `GET api/v1/admin/user` ### Changed diff --git a/apps/api/src/app/admin/admin.service.ts b/apps/api/src/app/admin/admin.service.ts index a0531c48..4abd4756 100644 --- a/apps/api/src/app/admin/admin.service.ts +++ b/apps/api/src/app/admin/admin.service.ts @@ -140,7 +140,7 @@ export class AdminService { const [settings, transactionCount, userCount] = await Promise.all([ this.propertyService.get(), this.prismaService.order.count(), - this.prismaService.user.count() + this.countUsersWithAnalytics() ]); return { @@ -436,7 +436,12 @@ export class AdminService { skip?: number; take?: number; }): Promise { - return { users: await this.getUsersWithAnalytics({ skip, take }) }; + const [count, users] = await Promise.all([ + this.countUsersWithAnalytics(), + this.getUsersWithAnalytics({ skip, take }) + ]); + + return { count, users }; } public async patchAssetProfileData({ @@ -514,6 +519,22 @@ export class AdminService { return response; } + private async countUsersWithAnalytics() { + let where: Prisma.UserWhereInput; + + if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) { + where = { + NOT: { + Analytics: null + } + }; + } + + return this.prismaService.user.count({ + where + }); + } + private getExtendedPrismaClient() { Logger.debug('Connect extended prisma client', 'AdminService'); diff --git a/apps/client/src/app/services/admin.service.ts b/apps/client/src/app/services/admin.service.ts index 367c1b43..20cfa8ef 100644 --- a/apps/client/src/app/services/admin.service.ts +++ b/apps/client/src/app/services/admin.service.ts @@ -159,7 +159,7 @@ export class AdminService { public fetchUsers() { let params = new HttpParams(); - params = params.append('take', 100); + params = params.append('take', 30); return this.http.get('/api/v1/admin/user', { params }); } diff --git a/libs/common/src/lib/interfaces/admin-users.interface.ts b/libs/common/src/lib/interfaces/admin-users.interface.ts index 24eb45c8..8fde15e1 100644 --- a/libs/common/src/lib/interfaces/admin-users.interface.ts +++ b/libs/common/src/lib/interfaces/admin-users.interface.ts @@ -1,6 +1,7 @@ import { Role } from '@prisma/client'; export interface AdminUsers { + count: number; users: { accountCount: number; country: string; From 707c77f0cf5bb213f9abed30a15c6fa934c52b5f Mon Sep 17 00:00:00 2001 From: Jory Hogeveen Date: Sat, 23 Nov 2024 10:25:07 +0100 Subject: [PATCH 42/65] Feature/extend allocations by ETF holding with parent ETFs (#4044) * Extend allocations by ETF holding with parent ETFs * Update changelog --------- Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> --- CHANGELOG.md | 1 + .../allocations/allocations-page.component.ts | 34 ++++- .../allocations/allocations-page.html | 1 + apps/client/src/styles/table.scss | 27 ++-- .../holding-with-parents.interface.ts | 5 + libs/common/src/lib/interfaces/index.ts | 2 + .../top-holdings/top-holdings.component.html | 129 +++++++++++++++--- .../top-holdings/top-holdings.component.scss | 32 ++++- .../top-holdings/top-holdings.component.ts | 51 +++++-- 9 files changed, 236 insertions(+), 46 deletions(-) create mode 100644 libs/common/src/lib/interfaces/holding-with-parents.interface.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bad82b8..57d4e72e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Extended the allocations by ETF holding on the allocations page by the parent ETFs (experimental) - Upgraded `countries-and-timezones` from version `3.4.1` to `3.7.2` - Upgraded `Nx` from version `20.0.6` to `20.1.2` diff --git a/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts b/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts index e647c54f..fa5a4751 100644 --- a/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts +++ b/apps/client/src/app/pages/portfolio/allocations/allocations-page.component.ts @@ -7,7 +7,7 @@ import { MAX_TOP_HOLDINGS, UNKNOWN_KEY } from '@ghostfolio/common/config'; import { prettifySymbol } from '@ghostfolio/common/helper'; import { AssetProfileIdentifier, - Holding, + HoldingWithParents, PortfolioDetails, PortfolioPosition, User @@ -86,7 +86,7 @@ export class AllocationsPageComponent implements OnDestroy, OnInit { value: number; }; }; - public topHoldings: Holding[]; + public topHoldings: HoldingWithParents[]; public topHoldingsMap: { [name: string]: { name: string; value: number }; }; @@ -490,6 +490,36 @@ export class AllocationsPageComponent implements OnDestroy, OnInit { name, allocationInPercentage: this.totalValueInEtf > 0 ? value / this.totalValueInEtf : 0, + parents: Object.entries(this.portfolioDetails.holdings) + .map(([symbol, holding]) => { + if (holding.holdings.length > 0) { + const currentParentHolding = holding.holdings.find( + (parentHolding) => { + return parentHolding.name === name; + } + ); + + return currentParentHolding + ? { + allocationInPercentage: + currentParentHolding.valueInBaseCurrency / value, + name: holding.name, + position: holding, + symbol: prettifySymbol(symbol), + valueInBaseCurrency: + currentParentHolding.valueInBaseCurrency + } + : null; + } + + return null; + }) + .filter((item) => { + return item !== null; + }) + .sort((a, b) => { + return b.allocationInPercentage - a.allocationInPercentage; + }), valueInBaseCurrency: value }; }) diff --git a/apps/client/src/app/pages/portfolio/allocations/allocations-page.html b/apps/client/src/app/pages/portfolio/allocations/allocations-page.html index 3431501f..f2dff76f 100644 --- a/apps/client/src/app/pages/portfolio/allocations/allocations-page.html +++ b/apps/client/src/app/pages/portfolio/allocations/allocations-page.html @@ -347,6 +347,7 @@ [locale]="user?.settings?.locale" [pageSize]="10" [topHoldings]="topHoldings" + (holdingClicked)="onSymbolChartClicked($event)" /> diff --git a/apps/client/src/styles/table.scss b/apps/client/src/styles/table.scss index f232cb1a..8c0f5c28 100644 --- a/apps/client/src/styles/table.scss +++ b/apps/client/src/styles/table.scss @@ -1,5 +1,10 @@ @mixin gf-table($darkTheme: false) { --mat-table-background-color: var(--light-background); + --mat-table-background-color-even: rgba(var(--palette-foreground-base), 0.02); + --mat-table-background-color-hover: rgba( + var(--palette-foreground-base), + 0.04 + ); .mat-footer-row, .mat-row { @@ -21,16 +26,24 @@ .mat-mdc-row { &:nth-child(even) { - background-color: whitesmoke; + background-color: var(--mat-table-background-color-even); } &:hover { - background-color: #e6e6e6 !important; + background-color: var(--mat-table-background-color-hover) !important; } } @if $darkTheme { --mat-table-background-color: var(--dark-background); + --mat-table-background-color-even: rgba( + var(--palette-foreground-base-dark), + 0.02 + ); + --mat-table-background-color-hover: rgba( + var(--palette-foreground-base-dark), + 0.04 + ); .mat-mdc-footer-row { .mat-mdc-footer-cell { @@ -40,15 +53,5 @@ ); } } - - .mat-mdc-row { - &:nth-child(even) { - background-color: #222222; - } - - &:hover { - background-color: #303030 !important; - } - } } } diff --git a/libs/common/src/lib/interfaces/holding-with-parents.interface.ts b/libs/common/src/lib/interfaces/holding-with-parents.interface.ts new file mode 100644 index 00000000..df3f3296 --- /dev/null +++ b/libs/common/src/lib/interfaces/holding-with-parents.interface.ts @@ -0,0 +1,5 @@ +import { Holding } from './holding.interface'; + +export interface HoldingWithParents extends Holding { + parents?: Holding[]; +} diff --git a/libs/common/src/lib/interfaces/index.ts b/libs/common/src/lib/interfaces/index.ts index eca14706..eb28a6d1 100644 --- a/libs/common/src/lib/interfaces/index.ts +++ b/libs/common/src/lib/interfaces/index.ts @@ -19,6 +19,7 @@ import type { Export } from './export.interface'; import type { FilterGroup } from './filter-group.interface'; import type { Filter } from './filter.interface'; import type { HistoricalDataItem } from './historical-data-item.interface'; +import type { HoldingWithParents } from './holding-with-parents.interface'; import type { Holding } from './holding.interface'; import type { InfoItem } from './info-item.interface'; import type { InvestmentItem } from './investment-item.interface'; @@ -80,6 +81,7 @@ export { FilterGroup, HistoricalDataItem, Holding, + HoldingWithParents, ImportResponse, InfoItem, InvestmentItem, diff --git a/libs/ui/src/lib/top-holdings/top-holdings.component.html b/libs/ui/src/lib/top-holdings/top-holdings.component.html index 72463da4..d42d742b 100644 --- a/libs/ui/src/lib/top-holdings/top-holdings.component.html +++ b/libs/ui/src/lib/top-holdings/top-holdings.component.html @@ -1,14 +1,18 @@

    diff --git a/libs/ui/src/lib/top-holdings/top-holdings.component.scss b/libs/ui/src/lib/top-holdings/top-holdings.component.scss index 990b8b29..b3e811a2 100644 --- a/libs/ui/src/lib/top-holdings/top-holdings.component.scss +++ b/libs/ui/src/lib/top-holdings/top-holdings.component.scss @@ -1,11 +1,33 @@ :host { display: block; - .gf-table { - th { - ::ng-deep { - .mat-sort-header-container { - justify-content: inherit; + .holdings-table { + table-layout: auto; + + tr { + &:not(.expanded) + tr.holding-detail td { + border-bottom: 0; + } + + &.expanded { + > td { + font-weight: bold; + } + } + + &.holding-detail { + height: 0; + } + + .holding-parents-table { + --table-padding: 0.5em; + + tr { + height: auto; + + td { + padding: var(--table-padding); + } } } } diff --git a/libs/ui/src/lib/top-holdings/top-holdings.component.ts b/libs/ui/src/lib/top-holdings/top-holdings.component.ts index 0a3f0e97..3d3712bc 100644 --- a/libs/ui/src/lib/top-holdings/top-holdings.component.ts +++ b/libs/ui/src/lib/top-holdings/top-holdings.component.ts @@ -1,33 +1,56 @@ +import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module'; import { getLocale } from '@ghostfolio/common/helper'; -import { Holding } from '@ghostfolio/common/interfaces'; +import { + AssetProfileIdentifier, + HoldingWithParents, + PortfolioPosition +} from '@ghostfolio/common/interfaces'; import { GfValueComponent } from '@ghostfolio/ui/value'; +import { + animate, + state, + style, + transition, + trigger +} from '@angular/animations'; import { CommonModule } from '@angular/common'; import { CUSTOM_ELEMENTS_SCHEMA, ChangeDetectionStrategy, Component, + EventEmitter, Input, OnChanges, OnDestroy, + Output, ViewChild } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator'; -import { MatSort, MatSortModule } from '@angular/material/sort'; import { MatTableDataSource, MatTableModule } from '@angular/material/table'; -import { get } from 'lodash'; +import { DataSource } from '@prisma/client'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import { Subject } from 'rxjs'; @Component({ + animations: [ + trigger('detailExpand', [ + state('collapsed,void', style({ height: '0px', minHeight: '0' })), + state('expanded', style({ height: '*' })), + transition( + 'expanded <=> collapsed', + animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)') + ) + ]) + ], changeDetection: ChangeDetectionStrategy.OnPush, imports: [ CommonModule, + GfSymbolModule, GfValueComponent, MatButtonModule, MatPaginatorModule, - MatSortModule, MatTableModule, NgxSkeletonLoaderModule ], @@ -41,12 +64,20 @@ export class GfTopHoldingsComponent implements OnChanges, OnDestroy { @Input() baseCurrency: string; @Input() locale = getLocale(); @Input() pageSize = Number.MAX_SAFE_INTEGER; - @Input() topHoldings: Holding[]; + @Input() topHoldings: HoldingWithParents[]; + @Input() positions: { + [symbol: string]: Pick & { + dataSource?: DataSource; + name: string; + value: number; + }; + } = {}; + + @Output() holdingClicked = new EventEmitter(); @ViewChild(MatPaginator) paginator: MatPaginator; - @ViewChild(MatSort) sort: MatSort; - public dataSource = new MatTableDataSource(); + public dataSource = new MatTableDataSource(); public displayedColumns: string[] = [ 'name', 'valueInBaseCurrency', @@ -61,14 +92,16 @@ export class GfTopHoldingsComponent implements OnChanges, OnDestroy { this.dataSource = new MatTableDataSource(this.topHoldings); this.dataSource.paginator = this.paginator; - this.dataSource.sort = this.sort; - this.dataSource.sortingDataAccessor = get; if (this.topHoldings) { this.isLoading = false; } } + public onClickHolding(assetProfileIdentifier: AssetProfileIdentifier) { + this.holdingClicked.emit(assetProfileIdentifier); + } + public onShowAllHoldings() { this.pageSize = Number.MAX_SAFE_INTEGER; From a8ea8a459939c599bc02b764dfb12dbe24f17ee8 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 23 Nov 2024 20:35:51 +0100 Subject: [PATCH 43/65] Feature/refactor types in EOD historical data service (#4063) * Refactor types --- .../eod-historical-data/eod-historical-data.service.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/apps/api/src/services/data-provider/eod-historical-data/eod-historical-data.service.ts b/apps/api/src/services/data-provider/eod-historical-data/eod-historical-data.service.ts index 7329b821..2037316a 100644 --- a/apps/api/src/services/data-provider/eod-historical-data/eod-historical-data.service.ts +++ b/apps/api/src/services/data-provider/eod-historical-data/eod-historical-data.service.ts @@ -410,14 +410,12 @@ export class EodHistoricalDataService implements DataProviderInterface { return name; } - private async getSearchResult(aQuery: string): Promise< - (LookupItem & { + private async getSearchResult(aQuery: string) { + let searchResult: (LookupItem & { assetClass: AssetClass; assetSubClass: AssetSubClass; isin: string; - })[] - > { - let searchResult = []; + })[] = []; try { const abortController = new AbortController(); From 0bc52fd80e84fc101c1815d15522288fc7c291fd Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 24 Nov 2024 08:59:09 +0100 Subject: [PATCH 44/65] Feature/harmonize log messages in data provider services (#4064) * Harmonize log messages --- .../data-provider/coingecko/coingecko.service.ts | 12 ++++++------ .../eod-historical-data.service.ts | 6 +++--- .../financial-modeling-prep.service.ts | 6 +++--- .../data-provider/rapid-api/rapid-api.service.ts | 6 +++--- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/apps/api/src/services/data-provider/coingecko/coingecko.service.ts b/apps/api/src/services/data-provider/coingecko/coingecko.service.ts index 7d6f22c6..99198016 100644 --- a/apps/api/src/services/data-provider/coingecko/coingecko.service.ts +++ b/apps/api/src/services/data-provider/coingecko/coingecko.service.ts @@ -86,9 +86,9 @@ export class CoinGeckoService implements DataProviderInterface { let message = error; if (error?.code === 'ABORT_ERR') { - message = `RequestError: The operation to get the asset profile for ${symbol} was aborted because the request to the data provider took more than ${this.configurationService.get( - 'REQUEST_TIMEOUT' - )}ms`; + message = `RequestError: The operation to get the asset profile for ${symbol} was aborted because the request to the data provider took more than ${( + this.configurationService.get('REQUEST_TIMEOUT') / 1000 + ).toFixed(3)} seconds`; } Logger.error(message, 'CoinGeckoService'); @@ -255,9 +255,9 @@ export class CoinGeckoService implements DataProviderInterface { let message = error; if (error?.code === 'ABORT_ERR') { - message = `RequestError: The operation to search for ${query} was aborted because the request to the data provider took more than ${this.configurationService.get( - 'REQUEST_TIMEOUT' - )}ms`; + message = `RequestError: The operation to search for ${query} was aborted because the request to the data provider took more than ${( + this.configurationService.get('REQUEST_TIMEOUT') / 1000 + ).toFixed(3)} seconds`; } Logger.error(message, 'CoinGeckoService'); diff --git a/apps/api/src/services/data-provider/eod-historical-data/eod-historical-data.service.ts b/apps/api/src/services/data-provider/eod-historical-data/eod-historical-data.service.ts index 2037316a..1c5696da 100644 --- a/apps/api/src/services/data-provider/eod-historical-data/eod-historical-data.service.ts +++ b/apps/api/src/services/data-provider/eod-historical-data/eod-historical-data.service.ts @@ -454,9 +454,9 @@ export class EodHistoricalDataService implements DataProviderInterface { let message = error; if (error?.code === 'ABORT_ERR') { - message = `RequestError: The operation to search for ${aQuery} was aborted because the request to the data provider took more than ${this.configurationService.get( - 'REQUEST_TIMEOUT' - )}ms`; + message = `RequestError: The operation to search for ${aQuery} was aborted because the request to the data provider took more than ${( + this.configurationService.get('REQUEST_TIMEOUT') / 1000 + ).toFixed(3)} seconds`; } Logger.error(message, 'EodHistoricalDataService'); diff --git a/apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts b/apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts index 9334fc4c..a1774fc0 100644 --- a/apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts +++ b/apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts @@ -204,9 +204,9 @@ export class FinancialModelingPrepService implements DataProviderInterface { let message = error; if (error?.code === 'ABORT_ERR') { - message = `RequestError: The operation to search for ${query} was aborted because the request to the data provider took more than ${this.configurationService.get( - 'REQUEST_TIMEOUT' - )}ms`; + message = `RequestError: The operation to search for ${query} was aborted because the request to the data provider took more than ${( + this.configurationService.get('REQUEST_TIMEOUT') / 1000 + ).toFixed(3)} seconds`; } Logger.error(message, 'FinancialModelingPrepService'); diff --git a/apps/api/src/services/data-provider/rapid-api/rapid-api.service.ts b/apps/api/src/services/data-provider/rapid-api/rapid-api.service.ts index 29e7f4ee..e7abc0ff 100644 --- a/apps/api/src/services/data-provider/rapid-api/rapid-api.service.ts +++ b/apps/api/src/services/data-provider/rapid-api/rapid-api.service.ts @@ -159,9 +159,9 @@ export class RapidApiService implements DataProviderInterface { let message = error; if (error?.code === 'ABORT_ERR') { - message = `RequestError: The operation was aborted because the request to the data provider took more than ${this.configurationService.get( - 'REQUEST_TIMEOUT' - )}ms`; + message = `RequestError: The operation was aborted because the request to the data provider took more than ${( + this.configurationService.get('REQUEST_TIMEOUT') / 1000 + ).toFixed(3)} seconds`; } Logger.error(message, 'RapidApiService'); From 5f98dfa5d6476c8686e0087082a174448ebffc33 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 24 Nov 2024 19:49:08 +0100 Subject: [PATCH 45/65] Feature/set up Ghostfolio data provider (#4016) * Set up Ghostfolio data provider * Update translations * Update changelog --- CHANGELOG.md | 2 + apps/api/src/app/app.module.ts | 2 + .../ghostfolio/get-historical.dto.ts | 15 ++ .../ghostfolio/get-quotes.dto.ts | 10 + .../ghostfolio/ghostfolio.controller.ts | 158 +++++++++++ .../ghostfolio/ghostfolio.module.ts | 83 ++++++ .../ghostfolio/ghostfolio.service.ts | 250 ++++++++++++++++++ apps/api/src/app/import/import.service.ts | 3 +- apps/api/src/app/info/info.service.ts | 3 +- apps/api/src/app/user/user.service.ts | 5 +- .../configuration/configuration.service.ts | 3 + .../data-provider/data-provider.module.ts | 5 + .../data-provider/data-provider.service.ts | 33 ++- .../ghostfolio/ghostfolio.service.ts | 221 ++++++++++++++++ .../interfaces/environment.interface.ts | 1 + apps/client/src/app/app-routing.module.ts | 9 + .../admin-settings.component.html | 38 ++- .../admin-settings.component.ts | 85 +++++- ...ghostfolio-premium-api-dialog.component.ts | 20 ++ .../ghostfolio-premium-api-dialog.html | 14 +- apps/client/src/app/core/auth.interceptor.ts | 11 + .../src/app/core/http-response.interceptor.ts | 2 +- .../src/app/pages/api/api-page.component.ts | 110 ++++++++ apps/client/src/app/pages/api/api-page.html | 48 ++++ apps/client/src/app/pages/api/api-page.scss | 3 + apps/client/src/app/services/admin.service.ts | 25 +- apps/client/src/locales/messages.ca.xlf | 114 ++++++-- apps/client/src/locales/messages.de.xlf | 112 ++++++-- apps/client/src/locales/messages.es.xlf | 114 ++++++-- apps/client/src/locales/messages.fr.xlf | 114 ++++++-- apps/client/src/locales/messages.it.xlf | 114 ++++++-- apps/client/src/locales/messages.nl.xlf | 114 ++++++-- apps/client/src/locales/messages.pl.xlf | 114 ++++++-- apps/client/src/locales/messages.pt.xlf | 114 ++++++-- apps/client/src/locales/messages.tr.xlf | 114 ++++++-- apps/client/src/locales/messages.xlf | 104 ++++++-- apps/client/src/locales/messages.zh.xlf | 114 ++++++-- libs/common/src/lib/config.ts | 4 + libs/common/src/lib/interfaces/index.ts | 6 + ...er-ghostfolio-status-response.interface.ts | 4 + .../historical-response.interface.ts | 7 + .../responses/quotes-response.interface.ts | 5 + libs/common/src/lib/permissions.ts | 1 + .../src/lib/types/user-with-settings.type.ts | 1 + .../symbol-autocomplete.component.html | 3 + .../migration.sql | 2 + prisma/schema.prisma | 1 + 47 files changed, 2129 insertions(+), 306 deletions(-) create mode 100644 apps/api/src/app/endpoints/data-providers/ghostfolio/get-historical.dto.ts create mode 100644 apps/api/src/app/endpoints/data-providers/ghostfolio/get-quotes.dto.ts create mode 100644 apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.controller.ts create mode 100644 apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.module.ts create mode 100644 apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.service.ts create mode 100644 apps/api/src/services/data-provider/ghostfolio/ghostfolio.service.ts create mode 100644 apps/client/src/app/pages/api/api-page.component.ts create mode 100644 apps/client/src/app/pages/api/api-page.html create mode 100644 apps/client/src/app/pages/api/api-page.scss create mode 100644 libs/common/src/lib/interfaces/responses/data-provider-ghostfolio-status-response.interface.ts create mode 100644 libs/common/src/lib/interfaces/responses/historical-response.interface.ts create mode 100644 libs/common/src/lib/interfaces/responses/quotes-response.interface.ts create mode 100644 prisma/migrations/20241103110114_added_ghostfolio_to_data_source/migration.sql diff --git a/CHANGELOG.md b/CHANGELOG.md index 57d4e72e..96e16f18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,10 +11,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added pagination parameters (`skip`, `take`) to the endpoint `GET api/v1/admin/user` - Added pagination response (`count`) to the endpoint `GET api/v1/admin/user` +- Added `GHOSTFOLIO` as a new data source type ### Changed - Extended the allocations by ETF holding on the allocations page by the parent ETFs (experimental) +- Improved the language localization for German (`de`) - Upgraded `countries-and-timezones` from version `3.4.1` to `3.7.2` - Upgraded `Nx` from version `20.0.6` to `20.1.2` diff --git a/apps/api/src/app/app.module.ts b/apps/api/src/app/app.module.ts index 2803a058..4fbdafb0 100644 --- a/apps/api/src/app/app.module.ts +++ b/apps/api/src/app/app.module.ts @@ -31,6 +31,7 @@ import { AuthDeviceModule } from './auth-device/auth-device.module'; import { AuthModule } from './auth/auth.module'; import { BenchmarkModule } from './benchmark/benchmark.module'; import { CacheModule } from './cache/cache.module'; +import { GhostfolioModule } from './endpoints/data-providers/ghostfolio/ghostfolio.module'; import { PublicModule } from './endpoints/public/public.module'; import { ExchangeRateModule } from './exchange-rate/exchange-rate.module'; import { ExportModule } from './export/export.module'; @@ -76,6 +77,7 @@ import { UserModule } from './user/user.module'; ExchangeRateModule, ExchangeRateDataModule, ExportModule, + GhostfolioModule, HealthModule, ImportModule, InfoModule, diff --git a/apps/api/src/app/endpoints/data-providers/ghostfolio/get-historical.dto.ts b/apps/api/src/app/endpoints/data-providers/ghostfolio/get-historical.dto.ts new file mode 100644 index 00000000..385c51d5 --- /dev/null +++ b/apps/api/src/app/endpoints/data-providers/ghostfolio/get-historical.dto.ts @@ -0,0 +1,15 @@ +import { Granularity } from '@ghostfolio/common/types'; + +import { IsIn, IsISO8601, IsOptional } from 'class-validator'; + +export class GetHistoricalDto { + @IsISO8601() + from: string; + + @IsIn(['day', 'month'] as Granularity[]) + @IsOptional() + granularity: Granularity; + + @IsISO8601() + to: string; +} diff --git a/apps/api/src/app/endpoints/data-providers/ghostfolio/get-quotes.dto.ts b/apps/api/src/app/endpoints/data-providers/ghostfolio/get-quotes.dto.ts new file mode 100644 index 00000000..e83c1be8 --- /dev/null +++ b/apps/api/src/app/endpoints/data-providers/ghostfolio/get-quotes.dto.ts @@ -0,0 +1,10 @@ +import { Transform } from 'class-transformer'; +import { IsString } from 'class-validator'; + +export class GetQuotesDto { + @IsString({ each: true }) + @Transform(({ value }) => + typeof value === 'string' ? value.split(',') : value + ) + symbols: string[]; +} diff --git a/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.controller.ts b/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.controller.ts new file mode 100644 index 00000000..58a3224c --- /dev/null +++ b/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.controller.ts @@ -0,0 +1,158 @@ +import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator'; +import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard'; +import { parseDate } from '@ghostfolio/common/helper'; +import { + DataProviderGhostfolioStatusResponse, + HistoricalResponse, + LookupResponse, + QuotesResponse +} from '@ghostfolio/common/interfaces'; +import { permissions } from '@ghostfolio/common/permissions'; +import { RequestWithUser } from '@ghostfolio/common/types'; + +import { + Controller, + Get, + HttpException, + Inject, + Param, + Query, + UseGuards +} from '@nestjs/common'; +import { REQUEST } from '@nestjs/core'; +import { AuthGuard } from '@nestjs/passport'; +import { getReasonPhrase, StatusCodes } from 'http-status-codes'; + +import { GetHistoricalDto } from './get-historical.dto'; +import { GetQuotesDto } from './get-quotes.dto'; +import { GhostfolioService } from './ghostfolio.service'; + +@Controller('data-providers/ghostfolio') +export class GhostfolioController { + public constructor( + private readonly ghostfolioService: GhostfolioService, + @Inject(REQUEST) private readonly request: RequestWithUser + ) {} + + @Get('historical/:symbol') + @HasPermission(permissions.enableDataProviderGhostfolio) + @UseGuards(AuthGuard('jwt'), HasPermissionGuard) + public async getHistorical( + @Param('symbol') symbol: string, + @Query() query: GetHistoricalDto + ): Promise { + const maxDailyRequests = await this.ghostfolioService.getMaxDailyRequests(); + + if ( + this.request.user.dataProviderGhostfolioDailyRequests > maxDailyRequests + ) { + throw new HttpException( + getReasonPhrase(StatusCodes.TOO_MANY_REQUESTS), + StatusCodes.TOO_MANY_REQUESTS + ); + } + + try { + const historicalData = await this.ghostfolioService.getHistorical({ + symbol, + from: parseDate(query.from), + granularity: query.granularity, + to: parseDate(query.to) + }); + + await this.ghostfolioService.incrementDailyRequests({ + userId: this.request.user.id + }); + + return historicalData; + } catch { + throw new HttpException( + getReasonPhrase(StatusCodes.INTERNAL_SERVER_ERROR), + StatusCodes.INTERNAL_SERVER_ERROR + ); + } + } + + @Get('lookup') + @HasPermission(permissions.enableDataProviderGhostfolio) + @UseGuards(AuthGuard('jwt'), HasPermissionGuard) + public async lookupSymbol( + @Query('includeIndices') includeIndicesParam = 'false', + @Query('query') query = '' + ): Promise { + const includeIndices = includeIndicesParam === 'true'; + const maxDailyRequests = await this.ghostfolioService.getMaxDailyRequests(); + + if ( + this.request.user.dataProviderGhostfolioDailyRequests > maxDailyRequests + ) { + throw new HttpException( + getReasonPhrase(StatusCodes.TOO_MANY_REQUESTS), + StatusCodes.TOO_MANY_REQUESTS + ); + } + + try { + const result = await this.ghostfolioService.lookup({ + includeIndices, + query: query.toLowerCase() + }); + + await this.ghostfolioService.incrementDailyRequests({ + userId: this.request.user.id + }); + + return result; + } catch { + throw new HttpException( + getReasonPhrase(StatusCodes.INTERNAL_SERVER_ERROR), + StatusCodes.INTERNAL_SERVER_ERROR + ); + } + } + + @Get('quotes') + @HasPermission(permissions.enableDataProviderGhostfolio) + @UseGuards(AuthGuard('jwt'), HasPermissionGuard) + public async getQuotes( + @Query() query: GetQuotesDto + ): Promise { + const maxDailyRequests = await this.ghostfolioService.getMaxDailyRequests(); + + if ( + this.request.user.dataProviderGhostfolioDailyRequests > maxDailyRequests + ) { + throw new HttpException( + getReasonPhrase(StatusCodes.TOO_MANY_REQUESTS), + StatusCodes.TOO_MANY_REQUESTS + ); + } + + try { + const quotes = await this.ghostfolioService.getQuotes({ + symbols: query.symbols + }); + + await this.ghostfolioService.incrementDailyRequests({ + userId: this.request.user.id + }); + + return quotes; + } catch { + throw new HttpException( + getReasonPhrase(StatusCodes.INTERNAL_SERVER_ERROR), + StatusCodes.INTERNAL_SERVER_ERROR + ); + } + } + + @Get('status') + @HasPermission(permissions.enableDataProviderGhostfolio) + @UseGuards(AuthGuard('jwt'), HasPermissionGuard) + public async getStatus(): Promise { + return { + dailyRequests: this.request.user.dataProviderGhostfolioDailyRequests, + dailyRequestsMax: await this.ghostfolioService.getMaxDailyRequests() + }; + } +} diff --git a/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.module.ts b/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.module.ts new file mode 100644 index 00000000..01691bcf --- /dev/null +++ b/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.module.ts @@ -0,0 +1,83 @@ +import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module'; +import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; +import { CryptocurrencyModule } from '@ghostfolio/api/services/cryptocurrency/cryptocurrency.module'; +import { AlphaVantageService } from '@ghostfolio/api/services/data-provider/alpha-vantage/alpha-vantage.service'; +import { CoinGeckoService } from '@ghostfolio/api/services/data-provider/coingecko/coingecko.service'; +import { YahooFinanceDataEnhancerService } from '@ghostfolio/api/services/data-provider/data-enhancer/yahoo-finance/yahoo-finance.service'; +import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module'; +import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service'; +import { EodHistoricalDataService } from '@ghostfolio/api/services/data-provider/eod-historical-data/eod-historical-data.service'; +import { FinancialModelingPrepService } from '@ghostfolio/api/services/data-provider/financial-modeling-prep/financial-modeling-prep.service'; +import { GoogleSheetsService } from '@ghostfolio/api/services/data-provider/google-sheets/google-sheets.service'; +import { ManualService } from '@ghostfolio/api/services/data-provider/manual/manual.service'; +import { RapidApiService } from '@ghostfolio/api/services/data-provider/rapid-api/rapid-api.service'; +import { YahooFinanceService } from '@ghostfolio/api/services/data-provider/yahoo-finance/yahoo-finance.service'; +import { MarketDataModule } from '@ghostfolio/api/services/market-data/market-data.module'; +import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module'; +import { PropertyModule } from '@ghostfolio/api/services/property/property.module'; +import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module'; + +import { Module } from '@nestjs/common'; + +import { GhostfolioController } from './ghostfolio.controller'; +import { GhostfolioService } from './ghostfolio.service'; + +@Module({ + controllers: [GhostfolioController], + imports: [ + CryptocurrencyModule, + DataProviderModule, + MarketDataModule, + PrismaModule, + PropertyModule, + RedisCacheModule, + SymbolProfileModule + ], + providers: [ + AlphaVantageService, + CoinGeckoService, + ConfigurationService, + DataProviderService, + EodHistoricalDataService, + FinancialModelingPrepService, + GhostfolioService, + GoogleSheetsService, + ManualService, + RapidApiService, + YahooFinanceService, + YahooFinanceDataEnhancerService, + { + inject: [ + AlphaVantageService, + CoinGeckoService, + EodHistoricalDataService, + FinancialModelingPrepService, + GoogleSheetsService, + ManualService, + RapidApiService, + YahooFinanceService + ], + provide: 'DataProviderInterfaces', + useFactory: ( + alphaVantageService, + coinGeckoService, + eodHistoricalDataService, + financialModelingPrepService, + googleSheetsService, + manualService, + rapidApiService, + yahooFinanceService + ) => [ + alphaVantageService, + coinGeckoService, + eodHistoricalDataService, + financialModelingPrepService, + googleSheetsService, + manualService, + rapidApiService, + yahooFinanceService + ] + } + ] +}) +export class GhostfolioModule {} diff --git a/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.service.ts b/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.service.ts new file mode 100644 index 00000000..52baa10d --- /dev/null +++ b/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.service.ts @@ -0,0 +1,250 @@ +import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; +import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service'; +import { + GetHistoricalParams, + GetQuotesParams, + GetSearchParams +} from '@ghostfolio/api/services/data-provider/interfaces/data-provider.interface'; +import { IDataProviderHistoricalResponse } from '@ghostfolio/api/services/interfaces/interfaces'; +import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; +import { PropertyService } from '@ghostfolio/api/services/property/property.service'; +import { + DEFAULT_CURRENCY, + DERIVED_CURRENCIES +} from '@ghostfolio/common/config'; +import { PROPERTY_DATA_SOURCES_GHOSTFOLIO_DATA_PROVIDER_MAX_REQUESTS } from '@ghostfolio/common/config'; +import { + DataProviderInfo, + HistoricalResponse, + LookupItem, + LookupResponse, + QuotesResponse +} from '@ghostfolio/common/interfaces'; + +import { Injectable, Logger } from '@nestjs/common'; +import { DataSource } from '@prisma/client'; +import { Big } from 'big.js'; + +@Injectable() +export class GhostfolioService { + public constructor( + private readonly configurationService: ConfigurationService, + private readonly dataProviderService: DataProviderService, + private readonly prismaService: PrismaService, + private readonly propertyService: PropertyService + ) {} + + public async getHistorical({ + from, + granularity, + requestTimeout, + to, + symbol + }: GetHistoricalParams) { + const result: HistoricalResponse = { historicalData: {} }; + + try { + const promises: Promise<{ + [symbol: string]: { [date: string]: IDataProviderHistoricalResponse }; + }>[] = []; + + for (const dataProviderService of this.getDataProviderServices()) { + promises.push( + dataProviderService + .getHistorical({ + from, + granularity, + requestTimeout, + symbol, + to + }) + .then((historicalData) => { + result.historicalData = historicalData[symbol]; + + return historicalData; + }) + ); + } + + await Promise.all(promises); + + return result; + } catch (error) { + Logger.error(error, 'GhostfolioService'); + + throw error; + } + } + + public async getMaxDailyRequests() { + return parseInt( + ((await this.propertyService.getByKey( + PROPERTY_DATA_SOURCES_GHOSTFOLIO_DATA_PROVIDER_MAX_REQUESTS + )) as string) || '0', + 10 + ); + } + + public async getQuotes({ requestTimeout, symbols }: GetQuotesParams) { + const promises: Promise[] = []; + const results: QuotesResponse = { quotes: {} }; + + try { + for (const dataProvider of this.getDataProviderServices()) { + const maximumNumberOfSymbolsPerRequest = + dataProvider.getMaxNumberOfSymbolsPerRequest?.() ?? + Number.MAX_SAFE_INTEGER; + + for ( + let i = 0; + i < symbols.length; + i += maximumNumberOfSymbolsPerRequest + ) { + const symbolsChunk = symbols.slice( + i, + i + maximumNumberOfSymbolsPerRequest + ); + + const promise = Promise.resolve( + dataProvider.getQuotes({ requestTimeout, symbols: symbolsChunk }) + ); + + promises.push( + promise.then(async (result) => { + for (const [symbol, dataProviderResponse] of Object.entries( + result + )) { + dataProviderResponse.dataSource = 'GHOSTFOLIO'; + + if ( + [ + ...DERIVED_CURRENCIES.map(({ currency }) => { + return `${DEFAULT_CURRENCY}${currency}`; + }), + `${DEFAULT_CURRENCY}USX` + ].includes(symbol) + ) { + continue; + } + + results.quotes[symbol] = dataProviderResponse; + + for (const { + currency, + factor, + rootCurrency + } of DERIVED_CURRENCIES) { + if (symbol === `${DEFAULT_CURRENCY}${rootCurrency}`) { + results.quotes[`${DEFAULT_CURRENCY}${currency}`] = { + ...dataProviderResponse, + currency, + marketPrice: new Big( + result[`${DEFAULT_CURRENCY}${rootCurrency}`].marketPrice + ) + .mul(factor) + .toNumber(), + marketState: 'open' + }; + } + } + } + }) + ); + } + + await Promise.all(promises); + } + + return results; + } catch (error) { + Logger.error(error, 'GhostfolioService'); + + throw error; + } + } + + public async incrementDailyRequests({ userId }: { userId: string }) { + await this.prismaService.analytics.update({ + data: { + dataProviderGhostfolioDailyRequests: { increment: 1 }, + lastRequestAt: new Date() + }, + where: { userId } + }); + } + + public async lookup({ + includeIndices = false, + query + }: GetSearchParams): Promise { + const results: LookupResponse = { items: [] }; + + if (!query) { + return results; + } + + try { + let lookupItems: LookupItem[] = []; + const promises: Promise<{ items: LookupItem[] }>[] = []; + + if (query?.length < 2) { + return { items: lookupItems }; + } + + for (const dataProviderService of this.getDataProviderServices()) { + promises.push( + dataProviderService.search({ + includeIndices, + query + }) + ); + } + + const searchResults = await Promise.all(promises); + + for (const { items } of searchResults) { + if (items?.length > 0) { + lookupItems = lookupItems.concat(items); + } + } + + const filteredItems = lookupItems + .filter(({ currency }) => { + // Only allow symbols with supported currency + return currency ? true : false; + }) + .sort(({ name: name1 }, { name: name2 }) => { + return name1?.toLowerCase().localeCompare(name2?.toLowerCase()); + }) + .map((lookupItem) => { + lookupItem.dataProviderInfo = this.getDataProviderInfo(); + lookupItem.dataSource = 'GHOSTFOLIO'; + + return lookupItem; + }); + + results.items = filteredItems; + return results; + } catch (error) { + Logger.error(error, 'GhostfolioService'); + + throw error; + } + } + + private getDataProviderInfo(): DataProviderInfo { + return { + isPremium: false, + name: 'Ghostfolio Premium', + url: 'https://ghostfol.io' + }; + } + + private getDataProviderServices() { + return this.configurationService + .get('DATA_SOURCES_GHOSTFOLIO_DATA_PROVIDER') + .map((dataSource) => { + return this.dataProviderService.getDataProvider(DataSource[dataSource]); + }); + } +} diff --git a/apps/api/src/app/import/import.service.ts b/apps/api/src/app/import/import.service.ts index 30415970..e51696b5 100644 --- a/apps/api/src/app/import/import.service.ts +++ b/apps/api/src/app/import/import.service.ts @@ -582,12 +582,13 @@ export class ImportService { const assetProfiles: { [assetProfileIdentifier: string]: Partial; } = {}; + const dataSources = await this.dataProviderService.getDataSources(); for (const [ index, { currency, dataSource, symbol, type } ] of activitiesDto.entries()) { - if (!this.configurationService.get('DATA_SOURCES').includes(dataSource)) { + if (!dataSources.includes(dataSource)) { throw new Error( `activities.${index}.dataSource ("${dataSource}") is not valid` ); diff --git a/apps/api/src/app/info/info.service.ts b/apps/api/src/app/info/info.service.ts index 62a78d1d..904a9709 100644 --- a/apps/api/src/app/info/info.service.ts +++ b/apps/api/src/app/info/info.service.ts @@ -7,6 +7,7 @@ import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate- import { PropertyService } from '@ghostfolio/api/services/property/property.service'; import { DEFAULT_CURRENCY, + HEADER_KEY_TOKEN, PROPERTY_BETTER_UPTIME_MONITOR_ID, PROPERTY_COUNTRIES_OF_SUBSCRIBERS, PROPERTY_DEMO_USER_ID, @@ -347,7 +348,7 @@ export class InfoService { )}&to${format(new Date(), DATE_FORMAT)}`, { headers: { - Authorization: `Bearer ${this.configurationService.get( + [HEADER_KEY_TOKEN]: `Bearer ${this.configurationService.get( 'API_KEY_BETTER_UPTIME' )}` }, diff --git a/apps/api/src/app/user/user.service.ts b/apps/api/src/app/user/user.service.ts index 443a2a05..54dafda2 100644 --- a/apps/api/src/app/user/user.service.ts +++ b/apps/api/src/app/user/user.service.ts @@ -183,7 +183,9 @@ export class UserService { Settings: Settings as UserWithSettings['Settings'], thirdPartyId, updatedAt, - activityCount: Analytics?.activityCount + activityCount: Analytics?.activityCount, + dataProviderGhostfolioDailyRequests: + Analytics?.dataProviderGhostfolioDailyRequests }; if (user?.Settings) { @@ -307,6 +309,7 @@ export class UserService { // Reset holdings view mode user.Settings.settings.holdingsViewMode = undefined; } else if (user.subscription?.type === 'Premium') { + currentPermissions.push(permissions.enableDataProviderGhostfolio); currentPermissions.push(permissions.reportDataGlitch); currentPermissions = without( diff --git a/apps/api/src/services/configuration/configuration.service.ts b/apps/api/src/services/configuration/configuration.service.ts index 10810deb..acde7d82 100644 --- a/apps/api/src/services/configuration/configuration.service.ts +++ b/apps/api/src/services/configuration/configuration.service.ts @@ -35,6 +35,9 @@ export class ConfigurationService { DATA_SOURCES: json({ default: [DataSource.COINGECKO, DataSource.MANUAL, DataSource.YAHOO] }), + DATA_SOURCES_GHOSTFOLIO_DATA_PROVIDER: json({ + default: [] + }), ENABLE_FEATURE_FEAR_AND_GREED_INDEX: bool({ default: false }), ENABLE_FEATURE_READ_ONLY_MODE: bool({ default: false }), ENABLE_FEATURE_SOCIAL_LOGIN: bool({ default: false }), diff --git a/apps/api/src/services/data-provider/data-provider.module.ts b/apps/api/src/services/data-provider/data-provider.module.ts index dcfc756f..71b54f01 100644 --- a/apps/api/src/services/data-provider/data-provider.module.ts +++ b/apps/api/src/services/data-provider/data-provider.module.ts @@ -5,6 +5,7 @@ import { AlphaVantageService } from '@ghostfolio/api/services/data-provider/alph import { CoinGeckoService } from '@ghostfolio/api/services/data-provider/coingecko/coingecko.service'; import { EodHistoricalDataService } from '@ghostfolio/api/services/data-provider/eod-historical-data/eod-historical-data.service'; import { FinancialModelingPrepService } from '@ghostfolio/api/services/data-provider/financial-modeling-prep/financial-modeling-prep.service'; +import { GhostfolioService } from '@ghostfolio/api/services/data-provider/ghostfolio/ghostfolio.service'; import { GoogleSheetsService } from '@ghostfolio/api/services/data-provider/google-sheets/google-sheets.service'; import { ManualService } from '@ghostfolio/api/services/data-provider/manual/manual.service'; import { RapidApiService } from '@ghostfolio/api/services/data-provider/rapid-api/rapid-api.service'; @@ -37,6 +38,7 @@ import { DataProviderService } from './data-provider.service'; DataProviderService, EodHistoricalDataService, FinancialModelingPrepService, + GhostfolioService, GoogleSheetsService, ManualService, RapidApiService, @@ -47,6 +49,7 @@ import { DataProviderService } from './data-provider.service'; CoinGeckoService, EodHistoricalDataService, FinancialModelingPrepService, + GhostfolioService, GoogleSheetsService, ManualService, RapidApiService, @@ -58,6 +61,7 @@ import { DataProviderService } from './data-provider.service'; coinGeckoService, eodHistoricalDataService, financialModelingPrepService, + ghostfolioService, googleSheetsService, manualService, rapidApiService, @@ -67,6 +71,7 @@ import { DataProviderService } from './data-provider.service'; coinGeckoService, eodHistoricalDataService, financialModelingPrepService, + ghostfolioService, googleSheetsService, manualService, rapidApiService, diff --git a/apps/api/src/services/data-provider/data-provider.service.ts b/apps/api/src/services/data-provider/data-provider.service.ts index c8a7422d..3faf5b58 100644 --- a/apps/api/src/services/data-provider/data-provider.service.ts +++ b/apps/api/src/services/data-provider/data-provider.service.ts @@ -11,6 +11,7 @@ import { PropertyService } from '@ghostfolio/api/services/property/property.serv import { DEFAULT_CURRENCY, DERIVED_CURRENCIES, + PROPERTY_API_KEY_GHOSTFOLIO, PROPERTY_DATA_SOURCE_MAPPING } from '@ghostfolio/common/config'; import { @@ -153,6 +154,24 @@ export class DataProviderService { return DataSource[this.configurationService.get('DATA_SOURCE_IMPORT')]; } + public async getDataSources(): Promise { + const dataSources: DataSource[] = this.configurationService + .get('DATA_SOURCES') + .map((dataSource) => { + return DataSource[dataSource]; + }); + + const ghostfolioApiKey = (await this.propertyService.getByKey( + PROPERTY_API_KEY_GHOSTFOLIO + )) as string; + + if (ghostfolioApiKey) { + dataSources.push('GHOSTFOLIO'); + } + + return dataSources.sort(); + } + public async getDividends({ dataSource, from, @@ -589,11 +608,11 @@ export class DataProviderService { return { items: lookupItems }; } - const dataProviderServices = this.configurationService - .get('DATA_SOURCES') - .map((dataSource) => { - return this.getDataProvider(DataSource[dataSource]); - }); + const dataSources = await this.getDataSources(); + + const dataProviderServices = dataSources.map((dataSource) => { + return this.getDataProvider(DataSource[dataSource]); + }); for (const dataProviderService of dataProviderServices) { promises.push( @@ -606,11 +625,11 @@ export class DataProviderService { const searchResults = await Promise.all(promises); - searchResults.forEach(({ items }) => { + for (const { items } of searchResults) { if (items?.length > 0) { lookupItems = lookupItems.concat(items); } - }); + } const filteredItems = lookupItems .filter(({ currency }) => { diff --git a/apps/api/src/services/data-provider/ghostfolio/ghostfolio.service.ts b/apps/api/src/services/data-provider/ghostfolio/ghostfolio.service.ts new file mode 100644 index 00000000..a1ac6b65 --- /dev/null +++ b/apps/api/src/services/data-provider/ghostfolio/ghostfolio.service.ts @@ -0,0 +1,221 @@ +import { environment } from '@ghostfolio/api/environments/environment'; +import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; +import { + DataProviderInterface, + GetDividendsParams, + GetHistoricalParams, + GetQuotesParams, + GetSearchParams +} from '@ghostfolio/api/services/data-provider/interfaces/data-provider.interface'; +import { + IDataProviderHistoricalResponse, + IDataProviderResponse +} from '@ghostfolio/api/services/interfaces/interfaces'; +import { PropertyService } from '@ghostfolio/api/services/property/property.service'; +import { + HEADER_KEY_TOKEN, + PROPERTY_API_KEY_GHOSTFOLIO +} from '@ghostfolio/common/config'; +import { DATE_FORMAT } from '@ghostfolio/common/helper'; +import { + DataProviderInfo, + HistoricalResponse, + LookupResponse, + QuotesResponse +} from '@ghostfolio/common/interfaces'; + +import { Injectable, Logger } from '@nestjs/common'; +import { DataSource, SymbolProfile } from '@prisma/client'; +import { format } from 'date-fns'; +import got from 'got'; + +@Injectable() +export class GhostfolioService implements DataProviderInterface { + private apiKey: string; + private readonly URL = environment.production + ? 'https://ghostfol.io/api' + : `${this.configurationService.get('ROOT_URL')}/api`; + + public constructor( + private readonly configurationService: ConfigurationService, + private readonly propertyService: PropertyService + ) { + void this.initialize(); + } + + public async initialize() { + this.apiKey = (await this.propertyService.getByKey( + PROPERTY_API_KEY_GHOSTFOLIO + )) as string; + } + + public canHandle() { + return true; + } + + public async getAssetProfile({ + symbol + }: { + symbol: string; + }): Promise> { + const { items } = await this.search({ query: symbol }); + const searchResult = items?.[0]; + + return { + symbol, + assetClass: searchResult?.assetClass, + assetSubClass: searchResult?.assetSubClass, + currency: searchResult?.currency, + dataSource: this.getName(), + name: searchResult?.name + }; + } + + public getDataProviderInfo(): DataProviderInfo { + return { + isPremium: true, + name: 'Ghostfolio', + url: 'https://ghostfo.io' + }; + } + + public async getDividends({}: GetDividendsParams) { + return {}; + } + + public async getHistorical({ + from, + granularity = 'day', + requestTimeout = this.configurationService.get('REQUEST_TIMEOUT'), + symbol, + to + }: GetHistoricalParams): Promise<{ + [symbol: string]: { [date: string]: IDataProviderHistoricalResponse }; + }> { + try { + const abortController = new AbortController(); + + setTimeout(() => { + abortController.abort(); + }, requestTimeout); + + const { historicalData } = await got( + `${this.URL}/v1/data-providers/ghostfolio/historical/${symbol}?from=${format(from, DATE_FORMAT)}&granularity=${granularity}&to=${format( + to, + DATE_FORMAT + )}`, + { + headers: this.getRequestHeaders(), + // @ts-ignore + signal: abortController.signal + } + ).json(); + + return { + [symbol]: historicalData + }; + } catch (error) { + throw new Error( + `Could not get historical market data for ${symbol} (${this.getName()}) from ${format( + from, + DATE_FORMAT + )} to ${format(to, DATE_FORMAT)}: [${error.name}] ${error.message}` + ); + } + } + + public getMaxNumberOfSymbolsPerRequest() { + return 20; + } + + public getName(): DataSource { + return DataSource.GHOSTFOLIO; + } + + public async getQuotes({ + requestTimeout = this.configurationService.get('REQUEST_TIMEOUT'), + symbols + }: GetQuotesParams): Promise<{ + [symbol: string]: IDataProviderResponse; + }> { + let response: { [symbol: string]: IDataProviderResponse } = {}; + + if (symbols.length <= 0) { + return response; + } + + try { + const abortController = new AbortController(); + + setTimeout(() => { + abortController.abort(); + }, requestTimeout); + + const { quotes } = await got( + `${this.URL}/v1/data-providers/ghostfolio/quotes?symbols=${symbols.join(',')}`, + { + headers: this.getRequestHeaders(), + // @ts-ignore + signal: abortController.signal + } + ).json(); + + response = quotes; + } catch (error) { + let message = error; + + if (error?.code === 'ABORT_ERR') { + message = `RequestError: The operation to get the quotes was aborted because the request to the data provider took more than ${( + this.configurationService.get('REQUEST_TIMEOUT') / 1000 + ).toFixed(3)} seconds`; + } + + Logger.error(message, 'GhostfolioService'); + } + + return response; + } + + public getTestSymbol() { + return 'AAPL.US'; + } + + public async search({ query }: GetSearchParams): Promise { + let searchResult: LookupResponse = { items: [] }; + + try { + const abortController = new AbortController(); + + setTimeout(() => { + abortController.abort(); + }, this.configurationService.get('REQUEST_TIMEOUT')); + + searchResult = await got( + `${this.URL}/v1/data-providers/ghostfolio/lookup?query=${query}`, + { + headers: this.getRequestHeaders(), + // @ts-ignore + signal: abortController.signal + } + ).json(); + } catch (error) { + let message = error; + + if (error?.code === 'ABORT_ERR') { + message = `RequestError: The operation to search for ${query} was aborted because the request to the data provider took more than ${( + this.configurationService.get('REQUEST_TIMEOUT') / 1000 + ).toFixed(3)} seconds`; + } + + Logger.error(message, 'GhostfolioService'); + } + + return searchResult; + } + + private getRequestHeaders() { + return { + [HEADER_KEY_TOKEN]: `Bearer ${this.apiKey}` + }; + } +} diff --git a/apps/api/src/services/interfaces/environment.interface.ts b/apps/api/src/services/interfaces/environment.interface.ts index 8d6dd34d..2f94739f 100644 --- a/apps/api/src/services/interfaces/environment.interface.ts +++ b/apps/api/src/services/interfaces/environment.interface.ts @@ -15,6 +15,7 @@ export interface Environment extends CleanedEnvAccessors { DATA_SOURCE_EXCHANGE_RATES: string; DATA_SOURCE_IMPORT: string; DATA_SOURCES: string[]; + DATA_SOURCES_GHOSTFOLIO_DATA_PROVIDER: string[]; ENABLE_FEATURE_FEAR_AND_GREED_INDEX: boolean; ENABLE_FEATURE_READ_ONLY_MODE: boolean; ENABLE_FEATURE_SOCIAL_LOGIN: boolean; diff --git a/apps/client/src/app/app-routing.module.ts b/apps/client/src/app/app-routing.module.ts index 8a517c5f..f4b61ea3 100644 --- a/apps/client/src/app/app-routing.module.ts +++ b/apps/client/src/app/app-routing.module.ts @@ -32,6 +32,15 @@ const routes: Routes = [ loadChildren: () => import('./pages/admin/admin-page.module').then((m) => m.AdminPageModule) }, + { + canActivate: [AuthGuard], + loadComponent: () => + import('./pages/api/api-page.component').then( + (c) => c.GfApiPageComponent + ), + path: 'api', + title: 'Ghostfolio API' + }, { path: 'auth', loadChildren: () => diff --git a/apps/client/src/app/components/admin-settings/admin-settings.component.html b/apps/client/src/app/components/admin-settings/admin-settings.component.html index b3a63df7..35ed556b 100644 --- a/apps/client/src/app/components/admin-settings/admin-settings.component.html +++ b/apps/client/src/app/components/admin-settings/admin-settings.component.html @@ -11,7 +11,9 @@ target="_blank" [href]="pricingUrl" > - NEW + @if (isGhostfolioApiKeyValid === false) { + NEW + } Ghostfolio Premium
    - + @if (isGhostfolioApiKeyValid === true) { +
    +
    + {{ ghostfolioApiStatus.dailyRequests }} + of + {{ ghostfolioApiStatus.dailyRequestsMax }} + daily requests +
    + +
    + } @else if (isGhostfolioApiKeyValid === false) { + + }
    diff --git a/apps/client/src/app/components/admin-settings/admin-settings.component.ts b/apps/client/src/app/components/admin-settings/admin-settings.component.ts index 2dd2555b..d25cdfbc 100644 --- a/apps/client/src/app/components/admin-settings/admin-settings.component.ts +++ b/apps/client/src/app/components/admin-settings/admin-settings.component.ts @@ -1,5 +1,13 @@ +import { ConfirmationDialogType } from '@ghostfolio/client/core/notification/confirmation-dialog/confirmation-dialog.type'; +import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; +import { AdminService } from '@ghostfolio/client/services/admin.service'; +import { DataService } from '@ghostfolio/client/services/data.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; -import { User } from '@ghostfolio/common/interfaces'; +import { PROPERTY_API_KEY_GHOSTFOLIO } from '@ghostfolio/common/config'; +import { + DataProviderGhostfolioStatusResponse, + User +} from '@ghostfolio/common/interfaces'; import { ChangeDetectionStrategy, @@ -10,7 +18,7 @@ import { } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; import { DeviceDetectorService } from 'ngx-device-detector'; -import { Subject, takeUntil } from 'rxjs'; +import { catchError, filter, of, Subject, takeUntil } from 'rxjs'; import { GfGhostfolioPremiumApiDialogComponent } from './ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.component'; @@ -21,6 +29,8 @@ import { GfGhostfolioPremiumApiDialogComponent } from './ghostfolio-premium-api- templateUrl: './admin-settings.component.html' }) export class AdminSettingsComponent implements OnDestroy, OnInit { + public ghostfolioApiStatus: DataProviderGhostfolioStatusResponse; + public isGhostfolioApiKeyValid: boolean; public pricingUrl: string; private deviceType: string; @@ -28,9 +38,12 @@ export class AdminSettingsComponent implements OnDestroy, OnInit { private user: User; public constructor( + private adminService: AdminService, private changeDetectorRef: ChangeDetectorRef, + private dataService: DataService, private deviceService: DeviceDetectorService, private matDialog: MatDialog, + private notificationService: NotificationService, private userService: UserService ) {} @@ -50,22 +63,72 @@ export class AdminSettingsComponent implements OnDestroy, OnInit { this.changeDetectorRef.markForCheck(); } }); + + this.initialize(); + } + + public onRemoveGhostfolioApiKey() { + this.notificationService.confirm({ + confirmFn: () => { + this.dataService + .putAdminSetting(PROPERTY_API_KEY_GHOSTFOLIO, { value: undefined }) + .subscribe(() => { + this.initialize(); + }); + }, + confirmType: ConfirmationDialogType.Warn, + title: $localize`Do you really want to delete the API key?` + }); } public onSetGhostfolioApiKey() { - this.matDialog.open(GfGhostfolioPremiumApiDialogComponent, { - autoFocus: false, - data: { - deviceType: this.deviceType, - pricingUrl: this.pricingUrl - }, - height: this.deviceType === 'mobile' ? '98vh' : undefined, - width: this.deviceType === 'mobile' ? '100vw' : '50rem' - }); + const dialogRef = this.matDialog.open( + GfGhostfolioPremiumApiDialogComponent, + { + autoFocus: false, + data: { + deviceType: this.deviceType, + pricingUrl: this.pricingUrl + }, + height: this.deviceType === 'mobile' ? '98vh' : undefined, + width: this.deviceType === 'mobile' ? '100vw' : '50rem' + } + ); + + dialogRef + .afterClosed() + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe(() => { + this.initialize(); + }); } public ngOnDestroy() { this.unsubscribeSubject.next(); this.unsubscribeSubject.complete(); } + + private initialize() { + this.adminService + .fetchGhostfolioDataProviderStatus() + .pipe( + catchError(() => { + this.isGhostfolioApiKeyValid = false; + + this.changeDetectorRef.markForCheck(); + + return of(null); + }), + filter((status) => { + return status !== null; + }), + takeUntil(this.unsubscribeSubject) + ) + .subscribe((status) => { + this.ghostfolioApiStatus = status; + this.isGhostfolioApiKeyValid = true; + + this.changeDetectorRef.markForCheck(); + }); + } } diff --git a/apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.component.ts b/apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.component.ts index 856ddc85..f15866f1 100644 --- a/apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.component.ts +++ b/apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.component.ts @@ -1,3 +1,5 @@ +import { DataService } from '@ghostfolio/client/services/data.service'; +import { PROPERTY_API_KEY_GHOSTFOLIO } from '@ghostfolio/common/config'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; import { CommonModule } from '@angular/common'; @@ -30,10 +32,28 @@ import { GhostfolioPremiumApiDialogParams } from './interfaces/interfaces'; export class GfGhostfolioPremiumApiDialogComponent { public constructor( @Inject(MAT_DIALOG_DATA) public data: GhostfolioPremiumApiDialogParams, + private dataService: DataService, public dialogRef: MatDialogRef ) {} public onCancel() { this.dialogRef.close(); } + + public onSetGhostfolioApiKey() { + let ghostfolioApiKey = prompt( + $localize`Please enter your Ghostfolio API key:` + ); + ghostfolioApiKey = ghostfolioApiKey?.trim(); + + if (ghostfolioApiKey) { + this.dataService + .putAdminSetting(PROPERTY_API_KEY_GHOSTFOLIO, { + value: ghostfolioApiKey + }) + .subscribe(() => { + this.dialogRef.close(); + }); + } + } } diff --git a/apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html b/apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html index 25673075..f2f75375 100644 --- a/apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html +++ b/apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html @@ -29,9 +29,19 @@ href="mailto:hi@ghostfol.io?Subject=Ghostfolio Premium Data Provider&body=Hello%0D%0DPlease notify me as soon as the Ghostfolio Premium Data Provider is available.%0D%0DKind regards" i18n mat-flat-button + >Notify me - Notify me - +
    + or +
    +
    diff --git a/apps/client/src/app/core/auth.interceptor.ts b/apps/client/src/app/core/auth.interceptor.ts index b0dbdf64..7491cecf 100644 --- a/apps/client/src/app/core/auth.interceptor.ts +++ b/apps/client/src/app/core/auth.interceptor.ts @@ -2,6 +2,7 @@ import { ImpersonationStorageService } from '@ghostfolio/client/services/imperso import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service'; import { HEADER_KEY_IMPERSONATION, + HEADER_KEY_SKIP_INTERCEPTOR, HEADER_KEY_TIMEZONE, HEADER_KEY_TOKEN } from '@ghostfolio/common/config'; @@ -27,6 +28,16 @@ export class AuthInterceptor implements HttpInterceptor { next: HttpHandler ): Observable> { let request = req; + + if (request.headers.has(HEADER_KEY_SKIP_INTERCEPTOR)) { + // Bypass the interceptor + request = request.clone({ + headers: req.headers.delete(HEADER_KEY_SKIP_INTERCEPTOR) + }); + + return next.handle(request); + } + let headers = request.headers.set( HEADER_KEY_TIMEZONE, Intl?.DateTimeFormat().resolvedOptions().timeZone diff --git a/apps/client/src/app/core/http-response.interceptor.ts b/apps/client/src/app/core/http-response.interceptor.ts index 0e24533e..203d3adf 100644 --- a/apps/client/src/app/core/http-response.interceptor.ts +++ b/apps/client/src/app/core/http-response.interceptor.ts @@ -103,7 +103,7 @@ export class HttpResponseInterceptor implements HttpInterceptor { } else if (error.status === StatusCodes.UNAUTHORIZED) { if (this.webAuthnService.isEnabled()) { this.router.navigate(['/webauthn']); - } else { + } else if (!error.url.includes('/data-providers/ghostfolio/status')) { this.tokenStorageService.signOut(); } } diff --git a/apps/client/src/app/pages/api/api-page.component.ts b/apps/client/src/app/pages/api/api-page.component.ts new file mode 100644 index 00000000..7b2d70ae --- /dev/null +++ b/apps/client/src/app/pages/api/api-page.component.ts @@ -0,0 +1,110 @@ +import { DATE_FORMAT } from '@ghostfolio/common/helper'; +import { + DataProviderGhostfolioStatusResponse, + HistoricalResponse, + LookupResponse, + QuotesResponse +} from '@ghostfolio/common/interfaces'; + +import { CommonModule } from '@angular/common'; +import { HttpClient, HttpParams } from '@angular/common/http'; +import { Component, OnInit } from '@angular/core'; +import { format, startOfYear } from 'date-fns'; +import { map, Observable, Subject, takeUntil } from 'rxjs'; + +@Component({ + host: { class: 'page' }, + imports: [CommonModule], + selector: 'gf-api-page', + standalone: true, + styleUrls: ['./api-page.scss'], + templateUrl: './api-page.html' +}) +export class GfApiPageComponent implements OnInit { + public historicalData$: Observable; + public quotes$: Observable; + public status$: Observable; + public symbols$: Observable; + + private unsubscribeSubject = new Subject(); + + public constructor(private http: HttpClient) {} + + public ngOnInit() { + this.historicalData$ = this.fetchHistoricalData({ symbol: 'AAPL.US' }); + this.quotes$ = this.fetchQuotes({ symbols: ['AAPL.US', 'VOO.US'] }); + this.status$ = this.fetchStatus(); + this.symbols$ = this.fetchSymbols({ query: 'apple' }); + } + + public ngOnDestroy() { + this.unsubscribeSubject.next(); + this.unsubscribeSubject.complete(); + } + + private fetchHistoricalData({ symbol }: { symbol: string }) { + const params = new HttpParams() + .set('from', format(startOfYear(new Date()), DATE_FORMAT)) + .set('to', format(new Date(), DATE_FORMAT)); + + return this.http + .get( + `/api/v1/data-providers/ghostfolio/historical/${symbol}`, + { params } + ) + .pipe( + map(({ historicalData }) => { + return historicalData; + }), + takeUntil(this.unsubscribeSubject) + ); + } + + private fetchQuotes({ symbols }: { symbols: string[] }) { + const params = new HttpParams().set('symbols', symbols.join(',')); + + return this.http + .get('/api/v1/data-providers/ghostfolio/quotes', { + params + }) + .pipe( + map(({ quotes }) => { + return quotes; + }), + takeUntil(this.unsubscribeSubject) + ); + } + + private fetchStatus() { + return this.http + .get( + '/api/v1/data-providers/ghostfolio/status' + ) + .pipe(takeUntil(this.unsubscribeSubject)); + } + + private fetchSymbols({ + includeIndices = false, + query + }: { + includeIndices?: boolean; + query: string; + }) { + let params = new HttpParams().set('query', query); + + if (includeIndices) { + params = params.append('includeIndices', includeIndices); + } + + return this.http + .get('/api/v1/data-providers/ghostfolio/lookup', { + params + }) + .pipe( + map(({ items }) => { + return items; + }), + takeUntil(this.unsubscribeSubject) + ); + } +} diff --git a/apps/client/src/app/pages/api/api-page.html b/apps/client/src/app/pages/api/api-page.html new file mode 100644 index 00000000..d7dca7fe --- /dev/null +++ b/apps/client/src/app/pages/api/api-page.html @@ -0,0 +1,48 @@ +
    +
    +

    Status

    +
    {{ status$ | async | json }}
    +
    +
    +

    Lookup

    + @if (symbols$) { + @let symbols = symbols$ | async; +
      + @for (item of symbols; track item.symbol) { +
    • {{ item.name }} ({{ item.symbol }})
    • + } +
    + } +
    +
    +

    Quotes

    + @if (quotes$) { + @let quotes = quotes$ | async; +
      + @for (quote of quotes | keyvalue; track quote) { +
    • + {{ quote.key }}: {{ quote.value.marketPrice }} + {{ quote.value.currency }} +
    • + } +
    + } +
    +
    +

    Historical

    + @if (historicalData$) { + @let historicalData = historicalData$ | async; +
      + @for ( + historicalDataItem of historicalData | keyvalue; + track historicalDataItem + ) { +
    • + {{ historicalDataItem.key }}: + {{ historicalDataItem.value.marketPrice }} +
    • + } +
    + } +
    +
    diff --git a/apps/client/src/app/pages/api/api-page.scss b/apps/client/src/app/pages/api/api-page.scss new file mode 100644 index 00000000..5d4e87f3 --- /dev/null +++ b/apps/client/src/app/pages/api/api-page.scss @@ -0,0 +1,3 @@ +:host { + display: block; +} diff --git a/apps/client/src/app/services/admin.service.ts b/apps/client/src/app/services/admin.service.ts index 20cfa8ef..d004671d 100644 --- a/apps/client/src/app/services/admin.service.ts +++ b/apps/client/src/app/services/admin.service.ts @@ -5,6 +5,11 @@ import { UpdatePlatformDto } from '@ghostfolio/api/app/platform/update-platform. import { CreateTagDto } from '@ghostfolio/api/app/tag/create-tag.dto'; import { UpdateTagDto } from '@ghostfolio/api/app/tag/update-tag.dto'; import { IDataProviderHistoricalResponse } from '@ghostfolio/api/services/interfaces/interfaces'; +import { + HEADER_KEY_SKIP_INTERCEPTOR, + HEADER_KEY_TOKEN, + PROPERTY_API_KEY_GHOSTFOLIO +} from '@ghostfolio/common/config'; import { DATE_FORMAT } from '@ghostfolio/common/helper'; import { AssetProfileIdentifier, @@ -13,6 +18,7 @@ import { AdminMarketData, AdminMarketDataDetails, AdminUsers, + DataProviderGhostfolioStatusResponse, EnhancedSymbolProfile, Filter } from '@ghostfolio/common/interfaces'; @@ -23,8 +29,9 @@ import { SortDirection } from '@angular/material/sort'; import { DataSource, MarketData, Platform, Tag } from '@prisma/client'; import { JobStatus } from 'bull'; import { format, parseISO } from 'date-fns'; -import { Observable, map } from 'rxjs'; +import { Observable, map, switchMap } from 'rxjs'; +import { environment } from '../../environments/environment'; import { DataService } from './data.service'; @Injectable({ @@ -136,6 +143,22 @@ export class AdminService { ); } + public fetchGhostfolioDataProviderStatus() { + return this.fetchAdminData().pipe( + switchMap(({ settings }) => { + return this.http.get( + `${environment.production ? 'https://ghostfol.io' : ''}/api/v1/data-providers/ghostfolio/status`, + { + headers: { + [HEADER_KEY_SKIP_INTERCEPTOR]: 'true', + [HEADER_KEY_TOKEN]: `Bearer ${settings[PROPERTY_API_KEY_GHOSTFOLIO]}` + } + } + ); + }) + ); + } + public fetchJobs({ status }: { status?: JobStatus[] }) { let params = new HttpParams(); diff --git a/apps/client/src/locales/messages.ca.xlf b/apps/client/src/locales/messages.ca.xlf index feff4f76..751ef908 100644 --- a/apps/client/src/locales/messages.ca.xlf +++ b/apps/client/src/locales/messages.ca.xlf @@ -6,7 +6,7 @@ Característiques apps/client/src/app/app-routing.module.ts - 65 + 74 @@ -14,7 +14,7 @@ Internacionalització apps/client/src/app/app-routing.module.ts - 79 + 88 @@ -22,7 +22,7 @@ Iniciar sessió apps/client/src/app/app-routing.module.ts - 141 + 150 apps/client/src/app/components/header/header.component.ts @@ -633,7 +633,7 @@ apps/client/src/app/components/admin-settings/admin-settings.component.ts - 48 + 61 apps/client/src/app/components/header/header.component.ts @@ -1051,7 +1051,11 @@ libs/ui/src/lib/top-holdings/top-holdings.component.html - 12 + 16 + + + libs/ui/src/lib/top-holdings/top-holdings.component.html + 88 @@ -1155,7 +1159,11 @@ libs/ui/src/lib/top-holdings/top-holdings.component.html - 26 + 25 + + + libs/ui/src/lib/top-holdings/top-holdings.component.html + 102 @@ -2191,7 +2199,7 @@ Plataformes apps/client/src/app/components/admin-settings/admin-settings.component.html - 39 + 59 @@ -2199,7 +2207,7 @@ Etiquetes apps/client/src/app/components/admin-settings/admin-settings.component.html - 45 + 65 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -2833,6 +2841,10 @@ or or + + apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html + 35 + apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html 31 @@ -4991,7 +5003,7 @@ libs/ui/src/lib/top-holdings/top-holdings.component.html - 88 + 181 @@ -6271,7 +6283,11 @@ libs/ui/src/lib/top-holdings/top-holdings.component.html - 46 + 40 + + + libs/ui/src/lib/top-holdings/top-holdings.component.html + 116 @@ -6743,7 +6759,7 @@ Show more libs/ui/src/lib/top-holdings/top-holdings.component.html - 81 + 174 @@ -7303,7 +7319,7 @@ Oops! Could not find any assets. libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html - 37 + 40 @@ -7319,15 +7335,15 @@ NEW apps/client/src/app/components/admin-settings/admin-settings.component.html - 14 + 15 - - Set API Key - Set API Key + + Set API key + Set API key apps/client/src/app/components/admin-settings/admin-settings.component.html - 29 + 48 @@ -7338,14 +7354,6 @@ 23 - - Notify me - Notify me - - apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html - 32 - - Get access to 100’000+ tickers from over 50 exchanges Get access to 100’000+ tickers from over 50 exchanges @@ -7452,6 +7460,62 @@ 69 + + of + of + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 29 + + + + daily requests + daily requests + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 31 + + + + Remove API key + Remove API key + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 38 + + + + Do you really want to delete the API key? + Do you really want to delete the API key? + + apps/client/src/app/components/admin-settings/admin-settings.component.ts + 80 + + + + Please enter your Ghostfolio API key: + Please enter your Ghostfolio API key: + + apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.component.ts + 45 + + + + Notify me + Notify me + + apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html + 32 + + + + I have an API key + I have an API key + + apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html + 42 + + diff --git a/apps/client/src/locales/messages.de.xlf b/apps/client/src/locales/messages.de.xlf index 796fe7b1..b57ae2c1 100644 --- a/apps/client/src/locales/messages.de.xlf +++ b/apps/client/src/locales/messages.de.xlf @@ -166,7 +166,11 @@ libs/ui/src/lib/top-holdings/top-holdings.component.html - 12 + 16 + + + libs/ui/src/lib/top-holdings/top-holdings.component.html + 88 @@ -238,7 +242,11 @@ libs/ui/src/lib/top-holdings/top-holdings.component.html - 26 + 25 + + + libs/ui/src/lib/top-holdings/top-holdings.component.html + 102 @@ -1034,7 +1042,7 @@ Einloggen apps/client/src/app/app-routing.module.ts - 141 + 150 apps/client/src/app/components/header/header.component.ts @@ -1096,6 +1104,10 @@ or oder + + apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html + 35 + apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html 31 @@ -1346,7 +1358,7 @@ Tags apps/client/src/app/components/admin-settings/admin-settings.component.html - 45 + 65 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1382,7 +1394,11 @@ libs/ui/src/lib/top-holdings/top-holdings.component.html - 46 + 40 + + + libs/ui/src/lib/top-holdings/top-holdings.component.html + 116 @@ -1966,7 +1982,7 @@ Features apps/client/src/app/app-routing.module.ts - 65 + 74 @@ -3982,7 +3998,7 @@ Plattformen apps/client/src/app/components/admin-settings/admin-settings.component.html - 39 + 59 @@ -4714,7 +4730,7 @@ libs/ui/src/lib/top-holdings/top-holdings.component.html - 88 + 181 @@ -5461,7 +5477,7 @@ apps/client/src/app/components/admin-settings/admin-settings.component.ts - 48 + 61 apps/client/src/app/components/header/header.component.ts @@ -6623,7 +6639,7 @@ Internationalisierung apps/client/src/app/app-routing.module.ts - 79 + 88 @@ -6687,7 +6703,7 @@ Mehr anzeigen libs/ui/src/lib/top-holdings/top-holdings.component.html - 81 + 174 @@ -7303,7 +7319,7 @@ Ups! Es konnten leider keine Assets gefunden werden. libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html - 37 + 40 @@ -7319,15 +7335,15 @@ NEU apps/client/src/app/components/admin-settings/admin-settings.component.html - 14 + 15 - - Set API Key + + Set API key API-Schlüssel setzen apps/client/src/app/components/admin-settings/admin-settings.component.html - 29 + 48 @@ -7338,14 +7354,6 @@ 23 - - Notify me - Benachrichtige mich - - apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html - 32 - - Get access to 100’000+ tickers from over 50 exchanges Erhalte Zugang zu 100’000+ Tickern von über 50 Handelsplätzen @@ -7452,6 +7460,62 @@ 69 + + of + von + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 29 + + + + daily requests + täglichen Anfragen + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 31 + + + + Remove API key + API-Schlüssel löschen + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 38 + + + + Do you really want to delete the API key? + Möchtest du den API-Schlüssel wirklich löschen? + + apps/client/src/app/components/admin-settings/admin-settings.component.ts + 80 + + + + Please enter your Ghostfolio API key: + Bitte gib den API-Schlüssel ein: + + apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.component.ts + 45 + + + + Notify me + Benachrichtige mich + + apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html + 32 + + + + I have an API key + Ich habe einen API-Schlüssel + + apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html + 42 + + diff --git a/apps/client/src/locales/messages.es.xlf b/apps/client/src/locales/messages.es.xlf index 70029fcc..13d3706b 100644 --- a/apps/client/src/locales/messages.es.xlf +++ b/apps/client/src/locales/messages.es.xlf @@ -167,7 +167,11 @@ libs/ui/src/lib/top-holdings/top-holdings.component.html - 12 + 16 + + + libs/ui/src/lib/top-holdings/top-holdings.component.html + 88 @@ -239,7 +243,11 @@ libs/ui/src/lib/top-holdings/top-holdings.component.html - 26 + 25 + + + libs/ui/src/lib/top-holdings/top-holdings.component.html + 102 @@ -1035,7 +1043,7 @@ Iniciar sesión apps/client/src/app/app-routing.module.ts - 141 + 150 apps/client/src/app/components/header/header.component.ts @@ -1097,6 +1105,10 @@ or o + + apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html + 35 + apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html 31 @@ -1347,7 +1359,7 @@ Etiquetas apps/client/src/app/components/admin-settings/admin-settings.component.html - 45 + 65 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1383,7 +1395,11 @@ libs/ui/src/lib/top-holdings/top-holdings.component.html - 46 + 40 + + + libs/ui/src/lib/top-holdings/top-holdings.component.html + 116 @@ -1967,7 +1983,7 @@ Funcionalidades apps/client/src/app/app-routing.module.ts - 65 + 74 @@ -3983,7 +3999,7 @@ Platforms apps/client/src/app/components/admin-settings/admin-settings.component.html - 39 + 59 @@ -4715,7 +4731,7 @@ libs/ui/src/lib/top-holdings/top-holdings.component.html - 88 + 181 @@ -5462,7 +5478,7 @@ apps/client/src/app/components/admin-settings/admin-settings.component.ts - 48 + 61 apps/client/src/app/components/header/header.component.ts @@ -6624,7 +6640,7 @@ Internacionalización apps/client/src/app/app-routing.module.ts - 79 + 88 @@ -6688,7 +6704,7 @@ Mostrar más libs/ui/src/lib/top-holdings/top-holdings.component.html - 81 + 174 @@ -7304,7 +7320,7 @@ Oops! Could not find any assets. libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html - 37 + 40 @@ -7320,15 +7336,15 @@ NEW apps/client/src/app/components/admin-settings/admin-settings.component.html - 14 + 15 - - Set API Key - Set API Key + + Set API key + Set API key apps/client/src/app/components/admin-settings/admin-settings.component.html - 29 + 48 @@ -7339,14 +7355,6 @@ 23 - - Notify me - Notify me - - apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html - 32 - - Get access to 100’000+ tickers from over 50 exchanges Get access to 100’000+ tickers from over 50 exchanges @@ -7453,6 +7461,62 @@ 69 + + of + of + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 29 + + + + daily requests + daily requests + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 31 + + + + Remove API key + Remove API key + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 38 + + + + Do you really want to delete the API key? + Do you really want to delete the API key? + + apps/client/src/app/components/admin-settings/admin-settings.component.ts + 80 + + + + Please enter your Ghostfolio API key: + Please enter your Ghostfolio API key: + + apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.component.ts + 45 + + + + Notify me + Notify me + + apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html + 32 + + + + I have an API key + I have an API key + + apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html + 42 + + diff --git a/apps/client/src/locales/messages.fr.xlf b/apps/client/src/locales/messages.fr.xlf index b442e461..ee5c6eb6 100644 --- a/apps/client/src/locales/messages.fr.xlf +++ b/apps/client/src/locales/messages.fr.xlf @@ -178,7 +178,11 @@ libs/ui/src/lib/top-holdings/top-holdings.component.html - 12 + 16 + + + libs/ui/src/lib/top-holdings/top-holdings.component.html + 88 @@ -298,7 +302,11 @@ libs/ui/src/lib/top-holdings/top-holdings.component.html - 26 + 25 + + + libs/ui/src/lib/top-holdings/top-holdings.component.html + 102 @@ -954,7 +962,7 @@ Étiquettes apps/client/src/app/components/admin-settings/admin-settings.component.html - 45 + 65 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1354,7 +1362,7 @@ Se connecter apps/client/src/app/app-routing.module.ts - 141 + 150 apps/client/src/app/components/header/header.component.ts @@ -1456,6 +1464,10 @@ or ou + + apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html + 35 + apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html 31 @@ -2378,7 +2390,7 @@ Fonctionnalités apps/client/src/app/app-routing.module.ts - 65 + 74 @@ -3150,7 +3162,11 @@ libs/ui/src/lib/top-holdings/top-holdings.component.html - 46 + 40 + + + libs/ui/src/lib/top-holdings/top-holdings.component.html + 116 @@ -3982,7 +3998,7 @@ Platformes apps/client/src/app/components/admin-settings/admin-settings.component.html - 39 + 59 @@ -4714,7 +4730,7 @@ libs/ui/src/lib/top-holdings/top-holdings.component.html - 88 + 181 @@ -5461,7 +5477,7 @@ apps/client/src/app/components/admin-settings/admin-settings.component.ts - 48 + 61 apps/client/src/app/components/header/header.component.ts @@ -6623,7 +6639,7 @@ Internationalisation apps/client/src/app/app-routing.module.ts - 79 + 88 @@ -6687,7 +6703,7 @@ Voir plus libs/ui/src/lib/top-holdings/top-holdings.component.html - 81 + 174 @@ -7303,7 +7319,7 @@ Oops! Could not find any assets. libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html - 37 + 40 @@ -7319,15 +7335,15 @@ NEW apps/client/src/app/components/admin-settings/admin-settings.component.html - 14 + 15 - - Set API Key - Set API Key + + Set API key + Set API key apps/client/src/app/components/admin-settings/admin-settings.component.html - 29 + 48 @@ -7338,14 +7354,6 @@ 23 - - Notify me - Notify me - - apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html - 32 - - Get access to 100’000+ tickers from over 50 exchanges Get access to 100’000+ tickers from over 50 exchanges @@ -7452,6 +7460,62 @@ 69 + + of + of + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 29 + + + + daily requests + daily requests + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 31 + + + + Remove API key + Remove API key + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 38 + + + + Do you really want to delete the API key? + Do you really want to delete the API key? + + apps/client/src/app/components/admin-settings/admin-settings.component.ts + 80 + + + + Please enter your Ghostfolio API key: + Please enter your Ghostfolio API key: + + apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.component.ts + 45 + + + + Notify me + Notify me + + apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html + 32 + + + + I have an API key + I have an API key + + apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html + 42 + + diff --git a/apps/client/src/locales/messages.it.xlf b/apps/client/src/locales/messages.it.xlf index 848e45d1..5d840375 100644 --- a/apps/client/src/locales/messages.it.xlf +++ b/apps/client/src/locales/messages.it.xlf @@ -167,7 +167,11 @@ libs/ui/src/lib/top-holdings/top-holdings.component.html - 12 + 16 + + + libs/ui/src/lib/top-holdings/top-holdings.component.html + 88 @@ -239,7 +243,11 @@ libs/ui/src/lib/top-holdings/top-holdings.component.html - 26 + 25 + + + libs/ui/src/lib/top-holdings/top-holdings.component.html + 102 @@ -1035,7 +1043,7 @@ Accedi apps/client/src/app/app-routing.module.ts - 141 + 150 apps/client/src/app/components/header/header.component.ts @@ -1097,6 +1105,10 @@ or oppure + + apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html + 35 + apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html 31 @@ -1347,7 +1359,7 @@ Tag apps/client/src/app/components/admin-settings/admin-settings.component.html - 45 + 65 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1383,7 +1395,11 @@ libs/ui/src/lib/top-holdings/top-holdings.component.html - 46 + 40 + + + libs/ui/src/lib/top-holdings/top-holdings.component.html + 116 @@ -1967,7 +1983,7 @@ Funzionalità apps/client/src/app/app-routing.module.ts - 65 + 74 @@ -3983,7 +3999,7 @@ Piattaforme apps/client/src/app/components/admin-settings/admin-settings.component.html - 39 + 59 @@ -4715,7 +4731,7 @@ libs/ui/src/lib/top-holdings/top-holdings.component.html - 88 + 181 @@ -5462,7 +5478,7 @@ apps/client/src/app/components/admin-settings/admin-settings.component.ts - 48 + 61 apps/client/src/app/components/header/header.component.ts @@ -6624,7 +6640,7 @@ Internazionalizzazione apps/client/src/app/app-routing.module.ts - 79 + 88 @@ -6688,7 +6704,7 @@ Visualizza di più libs/ui/src/lib/top-holdings/top-holdings.component.html - 81 + 174 @@ -7304,7 +7320,7 @@ Oops! Non ho trovato alcun asset. libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html - 37 + 40 @@ -7320,15 +7336,15 @@ NUOVO apps/client/src/app/components/admin-settings/admin-settings.component.html - 14 + 15 - - Set API Key - Imposta API Key + + Set API key + Imposta API Key apps/client/src/app/components/admin-settings/admin-settings.component.html - 29 + 48 @@ -7339,14 +7355,6 @@ 23 - - Notify me - Notificami - - apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html - 32 - - Get access to 100’000+ tickers from over 50 exchanges Ottieni accesso a oltre 100’000+ titoli da oltre 50 borse @@ -7453,6 +7461,62 @@ 69 + + of + of + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 29 + + + + daily requests + daily requests + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 31 + + + + Remove API key + Remove API key + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 38 + + + + Do you really want to delete the API key? + Do you really want to delete the API key? + + apps/client/src/app/components/admin-settings/admin-settings.component.ts + 80 + + + + Please enter your Ghostfolio API key: + Please enter your Ghostfolio API key: + + apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.component.ts + 45 + + + + Notify me + Notify me + + apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html + 32 + + + + I have an API key + I have an API key + + apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html + 42 + + diff --git a/apps/client/src/locales/messages.nl.xlf b/apps/client/src/locales/messages.nl.xlf index f590449d..b8dbd38a 100644 --- a/apps/client/src/locales/messages.nl.xlf +++ b/apps/client/src/locales/messages.nl.xlf @@ -166,7 +166,11 @@ libs/ui/src/lib/top-holdings/top-holdings.component.html - 12 + 16 + + + libs/ui/src/lib/top-holdings/top-holdings.component.html + 88 @@ -238,7 +242,11 @@ libs/ui/src/lib/top-holdings/top-holdings.component.html - 26 + 25 + + + libs/ui/src/lib/top-holdings/top-holdings.component.html + 102 @@ -1034,7 +1042,7 @@ Aanmelden apps/client/src/app/app-routing.module.ts - 141 + 150 apps/client/src/app/components/header/header.component.ts @@ -1096,6 +1104,10 @@ or of + + apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html + 35 + apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html 31 @@ -1346,7 +1358,7 @@ Tags apps/client/src/app/components/admin-settings/admin-settings.component.html - 45 + 65 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1382,7 +1394,11 @@ libs/ui/src/lib/top-holdings/top-holdings.component.html - 46 + 40 + + + libs/ui/src/lib/top-holdings/top-holdings.component.html + 116 @@ -1966,7 +1982,7 @@ Functionaliteiten apps/client/src/app/app-routing.module.ts - 65 + 74 @@ -3982,7 +3998,7 @@ Platforms apps/client/src/app/components/admin-settings/admin-settings.component.html - 39 + 59 @@ -4714,7 +4730,7 @@ libs/ui/src/lib/top-holdings/top-holdings.component.html - 88 + 181 @@ -5461,7 +5477,7 @@ apps/client/src/app/components/admin-settings/admin-settings.component.ts - 48 + 61 apps/client/src/app/components/header/header.component.ts @@ -6623,7 +6639,7 @@ Internationalization apps/client/src/app/app-routing.module.ts - 79 + 88 @@ -6687,7 +6703,7 @@ Show more libs/ui/src/lib/top-holdings/top-holdings.component.html - 81 + 174 @@ -7303,7 +7319,7 @@ Oops! Could not find any assets. libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html - 37 + 40 @@ -7319,15 +7335,15 @@ NEW apps/client/src/app/components/admin-settings/admin-settings.component.html - 14 + 15 - - Set API Key - Set API Key + + Set API key + Set API key apps/client/src/app/components/admin-settings/admin-settings.component.html - 29 + 48 @@ -7338,14 +7354,6 @@ 23 - - Notify me - Notify me - - apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html - 32 - - Get access to 100’000+ tickers from over 50 exchanges Get access to 100’000+ tickers from over 50 exchanges @@ -7452,6 +7460,62 @@ 69 + + of + of + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 29 + + + + daily requests + daily requests + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 31 + + + + Remove API key + Remove API key + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 38 + + + + Do you really want to delete the API key? + Do you really want to delete the API key? + + apps/client/src/app/components/admin-settings/admin-settings.component.ts + 80 + + + + Please enter your Ghostfolio API key: + Please enter your Ghostfolio API key: + + apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.component.ts + 45 + + + + Notify me + Notify me + + apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html + 32 + + + + I have an API key + I have an API key + + apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html + 42 + + diff --git a/apps/client/src/locales/messages.pl.xlf b/apps/client/src/locales/messages.pl.xlf index 04b15671..76e4decb 100644 --- a/apps/client/src/locales/messages.pl.xlf +++ b/apps/client/src/locales/messages.pl.xlf @@ -260,7 +260,7 @@ apps/client/src/app/components/admin-settings/admin-settings.component.ts - 48 + 61 apps/client/src/app/components/header/header.component.ts @@ -979,7 +979,11 @@ libs/ui/src/lib/top-holdings/top-holdings.component.html - 12 + 16 + + + libs/ui/src/lib/top-holdings/top-holdings.component.html + 88 @@ -1083,7 +1087,11 @@ libs/ui/src/lib/top-holdings/top-holdings.component.html - 26 + 25 + + + libs/ui/src/lib/top-holdings/top-holdings.component.html + 102 @@ -2019,7 +2027,7 @@ Platformy apps/client/src/app/components/admin-settings/admin-settings.component.html - 39 + 59 @@ -2027,7 +2035,7 @@ Tagi apps/client/src/app/components/admin-settings/admin-settings.component.html - 45 + 65 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -2295,7 +2303,7 @@ Zaloguj się apps/client/src/app/app-routing.module.ts - 141 + 150 apps/client/src/app/components/header/header.component.ts @@ -2481,6 +2489,10 @@ or lub + + apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html + 35 + apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html 31 @@ -3583,7 +3595,7 @@ Funkcje apps/client/src/app/app-routing.module.ts - 65 + 74 @@ -4599,7 +4611,7 @@ libs/ui/src/lib/top-holdings/top-holdings.component.html - 88 + 181 @@ -5675,7 +5687,11 @@ libs/ui/src/lib/top-holdings/top-holdings.component.html - 46 + 40 + + + libs/ui/src/lib/top-holdings/top-holdings.component.html + 116 @@ -6623,7 +6639,7 @@ Internacjonalizacja apps/client/src/app/app-routing.module.ts - 79 + 88 @@ -6687,7 +6703,7 @@ Pokaż więcej libs/ui/src/lib/top-holdings/top-holdings.component.html - 81 + 174 @@ -7303,7 +7319,7 @@ Oops! Could not find any assets. libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html - 37 + 40 @@ -7319,15 +7335,15 @@ NEW apps/client/src/app/components/admin-settings/admin-settings.component.html - 14 + 15 - - Set API Key - Set API Key + + Set API key + Set API key apps/client/src/app/components/admin-settings/admin-settings.component.html - 29 + 48 @@ -7338,14 +7354,6 @@ 23 - - Notify me - Notify me - - apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html - 32 - - Get access to 100’000+ tickers from over 50 exchanges Get access to 100’000+ tickers from over 50 exchanges @@ -7452,6 +7460,62 @@ 69 + + of + of + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 29 + + + + daily requests + daily requests + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 31 + + + + Remove API key + Remove API key + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 38 + + + + Do you really want to delete the API key? + Do you really want to delete the API key? + + apps/client/src/app/components/admin-settings/admin-settings.component.ts + 80 + + + + Please enter your Ghostfolio API key: + Please enter your Ghostfolio API key: + + apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.component.ts + 45 + + + + Notify me + Notify me + + apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html + 32 + + + + I have an API key + I have an API key + + apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html + 42 + + diff --git a/apps/client/src/locales/messages.pt.xlf b/apps/client/src/locales/messages.pt.xlf index bbaa17cd..404e4d2b 100644 --- a/apps/client/src/locales/messages.pt.xlf +++ b/apps/client/src/locales/messages.pt.xlf @@ -178,7 +178,11 @@ libs/ui/src/lib/top-holdings/top-holdings.component.html - 12 + 16 + + + libs/ui/src/lib/top-holdings/top-holdings.component.html + 88 @@ -298,7 +302,11 @@ libs/ui/src/lib/top-holdings/top-holdings.component.html - 26 + 25 + + + libs/ui/src/lib/top-holdings/top-holdings.component.html + 102 @@ -1218,7 +1226,7 @@ Iniciar sessão apps/client/src/app/app-routing.module.ts - 141 + 150 apps/client/src/app/components/header/header.component.ts @@ -1328,6 +1336,10 @@ or ou + + apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html + 35 + apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html 31 @@ -1650,7 +1662,7 @@ Marcadores apps/client/src/app/components/admin-settings/admin-settings.component.html - 45 + 65 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1686,7 +1698,11 @@ libs/ui/src/lib/top-holdings/top-holdings.component.html - 46 + 40 + + + libs/ui/src/lib/top-holdings/top-holdings.component.html + 116 @@ -2298,7 +2314,7 @@ Funcionalidades apps/client/src/app/app-routing.module.ts - 65 + 74 @@ -3982,7 +3998,7 @@ Plataformas apps/client/src/app/components/admin-settings/admin-settings.component.html - 39 + 59 @@ -4714,7 +4730,7 @@ libs/ui/src/lib/top-holdings/top-holdings.component.html - 88 + 181 @@ -5461,7 +5477,7 @@ apps/client/src/app/components/admin-settings/admin-settings.component.ts - 48 + 61 apps/client/src/app/components/header/header.component.ts @@ -6623,7 +6639,7 @@ Internationalization apps/client/src/app/app-routing.module.ts - 79 + 88 @@ -6687,7 +6703,7 @@ Show more libs/ui/src/lib/top-holdings/top-holdings.component.html - 81 + 174 @@ -7303,7 +7319,7 @@ Oops! Could not find any assets. libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html - 37 + 40 @@ -7319,15 +7335,15 @@ NEW apps/client/src/app/components/admin-settings/admin-settings.component.html - 14 + 15 - - Set API Key - Set API Key + + Set API key + Set API key apps/client/src/app/components/admin-settings/admin-settings.component.html - 29 + 48 @@ -7338,14 +7354,6 @@ 23 - - Notify me - Notify me - - apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html - 32 - - Get access to 100’000+ tickers from over 50 exchanges Get access to 100’000+ tickers from over 50 exchanges @@ -7452,6 +7460,62 @@ 69 + + of + of + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 29 + + + + daily requests + daily requests + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 31 + + + + Remove API key + Remove API key + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 38 + + + + Do you really want to delete the API key? + Do you really want to delete the API key? + + apps/client/src/app/components/admin-settings/admin-settings.component.ts + 80 + + + + Please enter your Ghostfolio API key: + Please enter your Ghostfolio API key: + + apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.component.ts + 45 + + + + Notify me + Notify me + + apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html + 32 + + + + I have an API key + I have an API key + + apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html + 42 + + diff --git a/apps/client/src/locales/messages.tr.xlf b/apps/client/src/locales/messages.tr.xlf index 3243ae46..307817d0 100644 --- a/apps/client/src/locales/messages.tr.xlf +++ b/apps/client/src/locales/messages.tr.xlf @@ -260,7 +260,7 @@ apps/client/src/app/components/admin-settings/admin-settings.component.ts - 48 + 61 apps/client/src/app/components/header/header.component.ts @@ -943,7 +943,11 @@ libs/ui/src/lib/top-holdings/top-holdings.component.html - 12 + 16 + + + libs/ui/src/lib/top-holdings/top-holdings.component.html + 88 @@ -1047,7 +1051,11 @@ libs/ui/src/lib/top-holdings/top-holdings.component.html - 26 + 25 + + + libs/ui/src/lib/top-holdings/top-holdings.component.html + 102 @@ -1763,7 +1771,7 @@ Etiketler apps/client/src/app/components/admin-settings/admin-settings.component.html - 45 + 65 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -1935,7 +1943,7 @@ Platformlar apps/client/src/app/components/admin-settings/admin-settings.component.html - 39 + 59 @@ -2147,7 +2155,7 @@ Giriş apps/client/src/app/app-routing.module.ts - 141 + 150 apps/client/src/app/components/header/header.component.ts @@ -2333,6 +2341,10 @@ or veya + + apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html + 35 + apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html 31 @@ -3135,7 +3147,7 @@ Özellikler apps/client/src/app/app-routing.module.ts - 65 + 74 @@ -4087,7 +4099,7 @@ libs/ui/src/lib/top-holdings/top-holdings.component.html - 88 + 181 @@ -5351,7 +5363,11 @@ libs/ui/src/lib/top-holdings/top-holdings.component.html - 46 + 40 + + + libs/ui/src/lib/top-holdings/top-holdings.component.html + 116 @@ -6623,7 +6639,7 @@ Internationalization apps/client/src/app/app-routing.module.ts - 79 + 88 @@ -6687,7 +6703,7 @@ Show more libs/ui/src/lib/top-holdings/top-holdings.component.html - 81 + 174 @@ -7303,7 +7319,7 @@ Oops! Could not find any assets. libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html - 37 + 40 @@ -7319,15 +7335,15 @@ NEW apps/client/src/app/components/admin-settings/admin-settings.component.html - 14 + 15 - - Set API Key - Set API Key + + Set API key + Set API key apps/client/src/app/components/admin-settings/admin-settings.component.html - 29 + 48 @@ -7338,14 +7354,6 @@ 23 - - Notify me - Notify me - - apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html - 32 - - Get access to 100’000+ tickers from over 50 exchanges Get access to 100’000+ tickers from over 50 exchanges @@ -7452,6 +7460,62 @@ 69 + + of + of + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 29 + + + + daily requests + daily requests + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 31 + + + + Remove API key + Remove API key + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 38 + + + + Do you really want to delete the API key? + Do you really want to delete the API key? + + apps/client/src/app/components/admin-settings/admin-settings.component.ts + 80 + + + + Please enter your Ghostfolio API key: + Please enter your Ghostfolio API key: + + apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.component.ts + 45 + + + + Notify me + Notify me + + apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html + 32 + + + + I have an API key + I have an API key + + apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html + 42 + + diff --git a/apps/client/src/locales/messages.xlf b/apps/client/src/locales/messages.xlf index db47d712..51a7c32e 100644 --- a/apps/client/src/locales/messages.xlf +++ b/apps/client/src/locales/messages.xlf @@ -255,7 +255,7 @@ apps/client/src/app/components/admin-settings/admin-settings.component.ts - 48 + 61 apps/client/src/app/components/header/header.component.ts @@ -951,7 +951,11 @@ libs/ui/src/lib/top-holdings/top-holdings.component.html - 12 + 16 + + + libs/ui/src/lib/top-holdings/top-holdings.component.html + 88 @@ -1052,7 +1056,11 @@ libs/ui/src/lib/top-holdings/top-holdings.component.html - 26 + 25 + + + libs/ui/src/lib/top-holdings/top-holdings.component.html + 102 @@ -1917,14 +1925,14 @@ Platforms apps/client/src/app/components/admin-settings/admin-settings.component.html - 39 + 59 Tags apps/client/src/app/components/admin-settings/admin-settings.component.html - 45 + 65 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -2165,7 +2173,7 @@ Sign in apps/client/src/app/app-routing.module.ts - 141 + 150 apps/client/src/app/components/header/header.component.ts @@ -2331,6 +2339,10 @@ or + + apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html + 35 + apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html 31 @@ -3326,7 +3338,7 @@ Features apps/client/src/app/app-routing.module.ts - 65 + 74 @@ -4231,7 +4243,7 @@ libs/ui/src/lib/top-holdings/top-holdings.component.html - 88 + 181 @@ -5237,7 +5249,11 @@ libs/ui/src/lib/top-holdings/top-holdings.component.html - 46 + 40 + + + libs/ui/src/lib/top-holdings/top-holdings.component.html + 116 @@ -6016,7 +6032,7 @@ Internationalization apps/client/src/app/app-routing.module.ts - 79 + 88 @@ -6072,7 +6088,7 @@ Show more libs/ui/src/lib/top-holdings/top-holdings.component.html - 81 + 174 @@ -6611,7 +6627,7 @@ Oops! Could not find any assets. libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html - 37 + 40 @@ -6621,13 +6637,6 @@ 23 - - Notify me - - apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html - 32 - - Ukraine @@ -6635,11 +6644,11 @@ 92 - - Set API Key + + Set API key apps/client/src/app/components/admin-settings/admin-settings.component.html - 29 + 48 @@ -6653,7 +6662,7 @@ NEW apps/client/src/app/components/admin-settings/admin-settings.component.html - 14 + 15 @@ -6744,6 +6753,55 @@ 5 + + Please enter your Ghostfolio API key: + + apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.component.ts + 45 + + + + of + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 29 + + + + Notify me + + apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html + 32 + + + + Do you really want to delete the API key? + + apps/client/src/app/components/admin-settings/admin-settings.component.ts + 80 + + + + I have an API key + + apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html + 42 + + + + Remove API key + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 38 + + + + daily requests + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 31 + + diff --git a/apps/client/src/locales/messages.zh.xlf b/apps/client/src/locales/messages.zh.xlf index a1c434da..d6b9d7bf 100644 --- a/apps/client/src/locales/messages.zh.xlf +++ b/apps/client/src/locales/messages.zh.xlf @@ -261,7 +261,7 @@ apps/client/src/app/components/admin-settings/admin-settings.component.ts - 48 + 61 apps/client/src/app/components/header/header.component.ts @@ -988,7 +988,11 @@ libs/ui/src/lib/top-holdings/top-holdings.component.html - 12 + 16 + + + libs/ui/src/lib/top-holdings/top-holdings.component.html + 88 @@ -1092,7 +1096,11 @@ libs/ui/src/lib/top-holdings/top-holdings.component.html - 26 + 25 + + + libs/ui/src/lib/top-holdings/top-holdings.component.html + 102 @@ -2036,7 +2044,7 @@ 平台 apps/client/src/app/components/admin-settings/admin-settings.component.html - 39 + 59 @@ -2044,7 +2052,7 @@ 标签 apps/client/src/app/components/admin-settings/admin-settings.component.html - 45 + 65 apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -2312,7 +2320,7 @@ 登入 apps/client/src/app/app-routing.module.ts - 141 + 150 apps/client/src/app/components/header/header.component.ts @@ -2498,6 +2506,10 @@ or + + apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html + 35 + apps/client/src/app/components/login-with-access-token-dialog/login-with-access-token-dialog.html 31 @@ -3600,7 +3612,7 @@ 功能 apps/client/src/app/app-routing.module.ts - 65 + 74 @@ -4616,7 +4628,7 @@ libs/ui/src/lib/top-holdings/top-holdings.component.html - 88 + 181 @@ -5740,7 +5752,11 @@ libs/ui/src/lib/top-holdings/top-holdings.component.html - 46 + 40 + + + libs/ui/src/lib/top-holdings/top-holdings.component.html + 116 @@ -6624,7 +6640,7 @@ Internationalization apps/client/src/app/app-routing.module.ts - 79 + 88 @@ -6688,7 +6704,7 @@ Show more libs/ui/src/lib/top-holdings/top-holdings.component.html - 81 + 174 @@ -7304,7 +7320,7 @@ Oops! Could not find any assets. libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html - 37 + 40 @@ -7320,15 +7336,15 @@ NEW apps/client/src/app/components/admin-settings/admin-settings.component.html - 14 + 15 - - Set API Key - Set API Key + + Set API key + Set API key apps/client/src/app/components/admin-settings/admin-settings.component.html - 29 + 48 @@ -7339,14 +7355,6 @@ 23 - - Notify me - Notify me - - apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html - 32 - - Get access to 100’000+ tickers from over 50 exchanges Get access to 100’000+ tickers from over 50 exchanges @@ -7453,6 +7461,62 @@ 69 + + of + of + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 29 + + + + daily requests + daily requests + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 31 + + + + Remove API key + Remove API key + + apps/client/src/app/components/admin-settings/admin-settings.component.html + 38 + + + + Do you really want to delete the API key? + Do you really want to delete the API key? + + apps/client/src/app/components/admin-settings/admin-settings.component.ts + 80 + + + + Please enter your Ghostfolio API key: + Please enter your Ghostfolio API key: + + apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.component.ts + 45 + + + + Notify me + Notify me + + apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html + 32 + + + + I have an API key + I have an API key + + apps/client/src/app/components/admin-settings/ghostfolio-premium-api-dialog/ghostfolio-premium-api-dialog.html + 42 + + diff --git a/libs/common/src/lib/config.ts b/libs/common/src/lib/config.ts index fbd416bb..7608e43a 100644 --- a/libs/common/src/lib/config.ts +++ b/libs/common/src/lib/config.ts @@ -106,17 +106,21 @@ export const PORTFOLIO_SNAPSHOT_PROCESS_JOB_OPTIONS: JobOptions = { export const HEADER_KEY_IMPERSONATION = 'Impersonation-Id'; export const HEADER_KEY_TIMEZONE = 'Timezone'; export const HEADER_KEY_TOKEN = 'Authorization'; +export const HEADER_KEY_SKIP_INTERCEPTOR = 'X-Skip-Interceptor'; export const MAX_TOP_HOLDINGS = 50; export const NUMERICAL_PRECISION_THRESHOLD = 100000; +export const PROPERTY_API_KEY_GHOSTFOLIO = 'API_KEY_GHOSTFOLIO'; export const PROPERTY_BENCHMARKS = 'BENCHMARKS'; export const PROPERTY_BETTER_UPTIME_MONITOR_ID = 'BETTER_UPTIME_MONITOR_ID'; export const PROPERTY_COUNTRIES_OF_SUBSCRIBERS = 'COUNTRIES_OF_SUBSCRIBERS'; export const PROPERTY_COUPONS = 'COUPONS'; export const PROPERTY_CURRENCIES = 'CURRENCIES'; export const PROPERTY_DATA_SOURCE_MAPPING = 'DATA_SOURCE_MAPPING'; +export const PROPERTY_DATA_SOURCES_GHOSTFOLIO_DATA_PROVIDER_MAX_REQUESTS = + 'DATA_SOURCES_GHOSTFOLIO_DATA_PROVIDER_MAX_REQUESTS'; export const PROPERTY_DEMO_USER_ID = 'DEMO_USER_ID'; export const PROPERTY_IS_DATA_GATHERING_ENABLED = 'IS_DATA_GATHERING_ENABLED'; export const PROPERTY_IS_READ_ONLY_MODE = 'IS_READ_ONLY_MODE'; diff --git a/libs/common/src/lib/interfaces/index.ts b/libs/common/src/lib/interfaces/index.ts index eb28a6d1..38093794 100644 --- a/libs/common/src/lib/interfaces/index.ts +++ b/libs/common/src/lib/interfaces/index.ts @@ -40,13 +40,16 @@ import type { Position } from './position.interface'; import type { Product } from './product'; import type { AccountBalancesResponse } from './responses/account-balances-response.interface'; import type { BenchmarkResponse } from './responses/benchmark-response.interface'; +import type { DataProviderGhostfolioStatusResponse } from './responses/data-provider-ghostfolio-status-response.interface'; import type { ResponseError } from './responses/errors.interface'; +import type { HistoricalResponse } from './responses/historical-response.interface'; import type { ImportResponse } from './responses/import-response.interface'; import type { LookupResponse } from './responses/lookup-response.interface'; import type { OAuthResponse } from './responses/oauth-response.interface'; import type { PortfolioHoldingsResponse } from './responses/portfolio-holdings-response.interface'; import type { PortfolioPerformanceResponse } from './responses/portfolio-performance-response.interface'; import type { PublicPortfolioResponse } from './responses/public-portfolio-response.interface'; +import type { QuotesResponse } from './responses/quotes-response.interface'; import type { ScraperConfiguration } from './scraper-configuration.interface'; import type { Statistics } from './statistics.interface'; import type { SubscriptionOffer } from './subscription-offer.interface'; @@ -74,12 +77,14 @@ export { BenchmarkProperty, BenchmarkResponse, Coupon, + DataProviderGhostfolioStatusResponse, DataProviderInfo, EnhancedSymbolProfile, Export, Filter, FilterGroup, HistoricalDataItem, + HistoricalResponse, Holding, HoldingWithParents, ImportResponse, @@ -105,6 +110,7 @@ export { Position, Product, PublicPortfolioResponse, + QuotesResponse, ResponseError, ScraperConfiguration, Statistics, diff --git a/libs/common/src/lib/interfaces/responses/data-provider-ghostfolio-status-response.interface.ts b/libs/common/src/lib/interfaces/responses/data-provider-ghostfolio-status-response.interface.ts new file mode 100644 index 00000000..11e9779d --- /dev/null +++ b/libs/common/src/lib/interfaces/responses/data-provider-ghostfolio-status-response.interface.ts @@ -0,0 +1,4 @@ +export interface DataProviderGhostfolioStatusResponse { + dailyRequests: number; + dailyRequestsMax: number; +} diff --git a/libs/common/src/lib/interfaces/responses/historical-response.interface.ts b/libs/common/src/lib/interfaces/responses/historical-response.interface.ts new file mode 100644 index 00000000..12309a35 --- /dev/null +++ b/libs/common/src/lib/interfaces/responses/historical-response.interface.ts @@ -0,0 +1,7 @@ +import { IDataProviderHistoricalResponse } from '@ghostfolio/api/services/interfaces/interfaces'; + +export interface HistoricalResponse { + historicalData: { + [date: string]: IDataProviderHistoricalResponse; + }; +} diff --git a/libs/common/src/lib/interfaces/responses/quotes-response.interface.ts b/libs/common/src/lib/interfaces/responses/quotes-response.interface.ts new file mode 100644 index 00000000..79c9d302 --- /dev/null +++ b/libs/common/src/lib/interfaces/responses/quotes-response.interface.ts @@ -0,0 +1,5 @@ +import { IDataProviderResponse } from '@ghostfolio/api/services/interfaces/interfaces'; + +export interface QuotesResponse { + quotes: { [symbol: string]: IDataProviderResponse }; +} diff --git a/libs/common/src/lib/permissions.ts b/libs/common/src/lib/permissions.ts index ab443ea5..1a81938b 100644 --- a/libs/common/src/lib/permissions.ts +++ b/libs/common/src/lib/permissions.ts @@ -22,6 +22,7 @@ export const permissions = { deletePlatform: 'deletePlatform', deleteTag: 'deleteTag', deleteUser: 'deleteUser', + enableDataProviderGhostfolio: 'enableDataProviderGhostfolio', enableFearAndGreedIndex: 'enableFearAndGreedIndex', enableImport: 'enableImport', enableBlog: 'enableBlog', diff --git a/libs/common/src/lib/types/user-with-settings.type.ts b/libs/common/src/lib/types/user-with-settings.type.ts index 2a669d26..5f983517 100644 --- a/libs/common/src/lib/types/user-with-settings.type.ts +++ b/libs/common/src/lib/types/user-with-settings.type.ts @@ -9,6 +9,7 @@ export type UserWithSettings = User & { Access: Access[]; Account: Account[]; activityCount: number; + dataProviderGhostfolioDailyRequests: number; permissions?: string[]; Settings: Settings & { settings: UserSettings }; subscription?: { diff --git a/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html b/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html index d055a618..9d1038e0 100644 --- a/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html +++ b/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html @@ -29,6 +29,9 @@ @if (lookupItem.assetSubClass) { · {{ lookupItem.assetSubClassString }} } + @if (lookupItem.dataProviderInfo.name) { + · {{ lookupItem.dataProviderInfo.name }} + } } @empty { diff --git a/prisma/migrations/20241103110114_added_ghostfolio_to_data_source/migration.sql b/prisma/migrations/20241103110114_added_ghostfolio_to_data_source/migration.sql new file mode 100644 index 00000000..9687a87b --- /dev/null +++ b/prisma/migrations/20241103110114_added_ghostfolio_to_data_source/migration.sql @@ -0,0 +1,2 @@ +-- AlterEnum +ALTER TYPE "DataSource" ADD VALUE 'GHOSTFOLIO'; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 5a34e8e1..5fe4ce7c 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -281,6 +281,7 @@ enum DataSource { COINGECKO EOD_HISTORICAL_DATA FINANCIAL_MODELING_PREP + GHOSTFOLIO GOOGLE_SHEETS MANUAL RAPID_API From 18de790f367902e66fd3fb9c2dae6a70c17db8de Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 24 Nov 2024 19:52:07 +0100 Subject: [PATCH 46/65] Release 2.124.0 (#4066) --- CHANGELOG.md | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96e16f18..d002e6fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ 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.124.0 - 2024-11-24 ### Added diff --git a/package-lock.json b/package-lock.json index 7e1d1cd9..e20a62d6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ghostfolio", - "version": "2.123.0", + "version": "2.124.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ghostfolio", - "version": "2.123.0", + "version": "2.124.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/package.json b/package.json index c2a6995a..4459c65a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ghostfolio", - "version": "2.123.0", + "version": "2.124.0", "homepage": "https://ghostfol.io", "license": "AGPL-3.0", "repository": "https://github.com/ghostfolio/ghostfolio", From cc88bd90696409e1da20dcc8cf896e8c34f70e6f Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 25 Nov 2024 21:13:22 +0100 Subject: [PATCH 47/65] Bugfix/fix background colors of sticky columns (#4068) * Fix background colors of sticky columns * Update changelog --- CHANGELOG.md | 6 ++++++ apps/client/src/styles/table.scss | 27 ++++++++++++--------------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d002e6fc..d1e31195 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ 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 + +### Fixed + +- Fixed the tables style related to sticky columns + ## 2.124.0 - 2024-11-24 ### Added diff --git a/apps/client/src/styles/table.scss b/apps/client/src/styles/table.scss index 8c0f5c28..f232cb1a 100644 --- a/apps/client/src/styles/table.scss +++ b/apps/client/src/styles/table.scss @@ -1,10 +1,5 @@ @mixin gf-table($darkTheme: false) { --mat-table-background-color: var(--light-background); - --mat-table-background-color-even: rgba(var(--palette-foreground-base), 0.02); - --mat-table-background-color-hover: rgba( - var(--palette-foreground-base), - 0.04 - ); .mat-footer-row, .mat-row { @@ -26,24 +21,16 @@ .mat-mdc-row { &:nth-child(even) { - background-color: var(--mat-table-background-color-even); + background-color: whitesmoke; } &:hover { - background-color: var(--mat-table-background-color-hover) !important; + background-color: #e6e6e6 !important; } } @if $darkTheme { --mat-table-background-color: var(--dark-background); - --mat-table-background-color-even: rgba( - var(--palette-foreground-base-dark), - 0.02 - ); - --mat-table-background-color-hover: rgba( - var(--palette-foreground-base-dark), - 0.04 - ); .mat-mdc-footer-row { .mat-mdc-footer-cell { @@ -53,5 +40,15 @@ ); } } + + .mat-mdc-row { + &:nth-child(even) { + background-color: #222222; + } + + &:hover { + background-color: #303030 !important; + } + } } } From 9ca901f6e66cbbeac86aa1627a985730d393ce39 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 25 Nov 2024 21:25:57 +0100 Subject: [PATCH 48/65] Feature/improve api key management of ghostfolio data provider (#4069) * Improve API key management * Add fallback for language code --- .../ghostfolio/ghostfolio.service.ts | 25 ++++++++----------- .../admin-settings.component.ts | 10 ++++++-- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/apps/api/src/services/data-provider/ghostfolio/ghostfolio.service.ts b/apps/api/src/services/data-provider/ghostfolio/ghostfolio.service.ts index a1ac6b65..acd66b0a 100644 --- a/apps/api/src/services/data-provider/ghostfolio/ghostfolio.service.ts +++ b/apps/api/src/services/data-provider/ghostfolio/ghostfolio.service.ts @@ -31,7 +31,6 @@ import got from 'got'; @Injectable() export class GhostfolioService implements DataProviderInterface { - private apiKey: string; private readonly URL = environment.production ? 'https://ghostfol.io/api' : `${this.configurationService.get('ROOT_URL')}/api`; @@ -39,15 +38,7 @@ export class GhostfolioService implements DataProviderInterface { public constructor( private readonly configurationService: ConfigurationService, private readonly propertyService: PropertyService - ) { - void this.initialize(); - } - - public async initialize() { - this.apiKey = (await this.propertyService.getByKey( - PROPERTY_API_KEY_GHOSTFOLIO - )) as string; - } + ) {} public canHandle() { return true; @@ -105,7 +96,7 @@ export class GhostfolioService implements DataProviderInterface { DATE_FORMAT )}`, { - headers: this.getRequestHeaders(), + headers: await this.getRequestHeaders(), // @ts-ignore signal: abortController.signal } @@ -154,7 +145,7 @@ export class GhostfolioService implements DataProviderInterface { const { quotes } = await got( `${this.URL}/v1/data-providers/ghostfolio/quotes?symbols=${symbols.join(',')}`, { - headers: this.getRequestHeaders(), + headers: await this.getRequestHeaders(), // @ts-ignore signal: abortController.signal } @@ -193,7 +184,7 @@ export class GhostfolioService implements DataProviderInterface { searchResult = await got( `${this.URL}/v1/data-providers/ghostfolio/lookup?query=${query}`, { - headers: this.getRequestHeaders(), + headers: await this.getRequestHeaders(), // @ts-ignore signal: abortController.signal } @@ -213,9 +204,13 @@ export class GhostfolioService implements DataProviderInterface { return searchResult; } - private getRequestHeaders() { + private async getRequestHeaders() { + const apiKey = (await this.propertyService.getByKey( + PROPERTY_API_KEY_GHOSTFOLIO + )) as string; + return { - [HEADER_KEY_TOKEN]: `Bearer ${this.apiKey}` + [HEADER_KEY_TOKEN]: `Bearer ${apiKey}` }; } } diff --git a/apps/client/src/app/components/admin-settings/admin-settings.component.ts b/apps/client/src/app/components/admin-settings/admin-settings.component.ts index d25cdfbc..68256bf9 100644 --- a/apps/client/src/app/components/admin-settings/admin-settings.component.ts +++ b/apps/client/src/app/components/admin-settings/admin-settings.component.ts @@ -3,7 +3,10 @@ import { NotificationService } from '@ghostfolio/client/core/notification/notifi import { AdminService } from '@ghostfolio/client/services/admin.service'; import { DataService } from '@ghostfolio/client/services/data.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; -import { PROPERTY_API_KEY_GHOSTFOLIO } from '@ghostfolio/common/config'; +import { + DEFAULT_LANGUAGE_CODE, + PROPERTY_API_KEY_GHOSTFOLIO +} from '@ghostfolio/common/config'; import { DataProviderGhostfolioStatusResponse, User @@ -56,8 +59,11 @@ export class AdminSettingsComponent implements OnDestroy, OnInit { if (state?.user) { this.user = state.user; + const languageCode = + this.user?.settings?.language ?? DEFAULT_LANGUAGE_CODE; + this.pricingUrl = - `https://ghostfol.io/${this.user.settings.language}/` + + `https://ghostfol.io/${languageCode}/` + $localize`:snake-case:pricing`; this.changeDetectorRef.markForCheck(); From e4e05fac94c51f9a749951e5bc6ce5ef4365cf68 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 25 Nov 2024 21:28:36 +0100 Subject: [PATCH 49/65] Release 2.124.1 (#4072) --- CHANGELOG.md | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1e31195..31fe9d11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ 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.124.1 - 2024-11-25 ### Fixed diff --git a/package-lock.json b/package-lock.json index e20a62d6..c6720ef9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ghostfolio", - "version": "2.124.0", + "version": "2.124.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ghostfolio", - "version": "2.124.0", + "version": "2.124.1", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/package.json b/package.json index 4459c65a..9332f33e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ghostfolio", - "version": "2.124.0", + "version": "2.124.1", "homepage": "https://ghostfol.io", "license": "AGPL-3.0", "repository": "https://github.com/ghostfolio/ghostfolio", From fc7e350cf7fac805076fddc769966266f5805f01 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 27 Nov 2024 08:10:20 +0100 Subject: [PATCH 50/65] Feature/extend users table of admin control panel (#4076) * Extend users table * Update changelog --- CHANGELOG.md | 6 ++++++ apps/api/src/app/admin/admin.service.ts | 2 ++ .../admin-users/admin-users.component.ts | 1 + .../components/admin-users/admin-users.html | 21 +++++++++++++++++++ .../lib/interfaces/admin-users.interface.ts | 1 + 5 files changed, 31 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 31fe9d11..bdd16fab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ 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 + +### Changed + +- Extended the users table in the admin control panel + ## 2.124.1 - 2024-11-25 ### Fixed diff --git a/apps/api/src/app/admin/admin.service.ts b/apps/api/src/app/admin/admin.service.ts index 4abd4756..fb6e90f5 100644 --- a/apps/api/src/app/admin/admin.service.ts +++ b/apps/api/src/app/admin/admin.service.ts @@ -705,6 +705,7 @@ export class AdminService { select: { activityCount: true, country: true, + dataProviderGhostfolioDailyRequests: true, updatedAt: true } }, @@ -740,6 +741,7 @@ export class AdminService { subscription, accountCount: _count.Account || 0, country: Analytics?.country, + dailyApiRequests: Analytics?.dataProviderGhostfolioDailyRequests || 0, lastActivity: Analytics?.updatedAt, transactionCount: _count.Order || 0 }; diff --git a/apps/client/src/app/components/admin-users/admin-users.component.ts b/apps/client/src/app/components/admin-users/admin-users.component.ts index 86759376..d619f4dd 100644 --- a/apps/client/src/app/components/admin-users/admin-users.component.ts +++ b/apps/client/src/app/components/admin-users/admin-users.component.ts @@ -60,6 +60,7 @@ export class AdminUsersComponent implements OnDestroy, OnInit { 'accounts', 'activities', 'engagementPerDay', + 'dailyApiRequests', 'lastRequest', 'actions' ]; diff --git a/apps/client/src/app/components/admin-users/admin-users.html b/apps/client/src/app/components/admin-users/admin-users.html index 2c806684..170b500f 100644 --- a/apps/client/src/app/components/admin-users/admin-users.html +++ b/apps/client/src/app/components/admin-users/admin-users.html @@ -169,6 +169,27 @@ /> + + + API Requests Today + + + + + } @if (hasPermissionForSubscription) { diff --git a/libs/common/src/lib/interfaces/admin-users.interface.ts b/libs/common/src/lib/interfaces/admin-users.interface.ts index 8fde15e1..89e16575 100644 --- a/libs/common/src/lib/interfaces/admin-users.interface.ts +++ b/libs/common/src/lib/interfaces/admin-users.interface.ts @@ -6,6 +6,7 @@ export interface AdminUsers { accountCount: number; country: string; createdAt: Date; + dailyApiRequests: number; engagement: number; id: string; lastActivity: Date; From 4e368e09a62b6a45d47a32a66b02e2f9f1d879b0 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Wed, 27 Nov 2024 17:44:59 +0100 Subject: [PATCH 51/65] Feature/improve style of symbol autocomplete component (#4070) * Improve style * Update changelog --- CHANGELOG.md | 1 + .../lib/symbol-autocomplete/symbol-autocomplete.component.html | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bdd16fab..f37d4f5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Improved the style of the symbol search component - Extended the users table in the admin control panel ## 2.124.1 - 2024-11-25 diff --git a/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html b/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html index 9d1038e0..c6327c31 100644 --- a/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html +++ b/libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html @@ -19,7 +19,7 @@ [value]="lookupItem" > {{ lookupItem.name }} + >{{ lookupItem.name }} @if (lookupItem.dataProviderInfo.isPremium) { } From 8fe767a4e3a94c1ee2f4f3b8bd2e46e2f3a6b25c Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Thu, 28 Nov 2024 20:19:37 +0100 Subject: [PATCH 52/65] Feature/increase default request timeout (#4075) * Increase default request timeout * Update changelog --- CHANGELOG.md | 1 + apps/api/src/services/configuration/configuration.service.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f37d4f5f..2ac35a53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Improved the style of the symbol search component - Extended the users table in the admin control panel +- Increased the default request timeout (`REQUEST_TIMEOUT`) ## 2.124.1 - 2024-11-25 diff --git a/apps/api/src/services/configuration/configuration.service.ts b/apps/api/src/services/configuration/configuration.service.ts index acde7d82..3dfe5d5c 100644 --- a/apps/api/src/services/configuration/configuration.service.ts +++ b/apps/api/src/services/configuration/configuration.service.ts @@ -70,7 +70,7 @@ export class ConfigurationService { REDIS_HOST: str({ default: 'localhost' }), REDIS_PASSWORD: str({ default: '' }), REDIS_PORT: port({ default: 6379 }), - REQUEST_TIMEOUT: num({ default: 2000 }), + REQUEST_TIMEOUT: num({ default: ms('3 seconds') }), ROOT_URL: url({ default: DEFAULT_ROOT_URL }), STRIPE_PUBLIC_KEY: str({ default: '' }), STRIPE_SECRET_KEY: str({ default: '' }), From 14eeec8f4f3c38ad932e4499ecd8096ab393137e Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Thu, 28 Nov 2024 20:38:31 +0100 Subject: [PATCH 53/65] Feature/upgrade cheerio to version 1.0.0 (#4060) * Upgrade cheerio to version 1.0.0 * Update changelog --- CHANGELOG.md | 1 + package-lock.json | 108 ++++++++++++++++++++++++++++++++++++++++++---- package.json | 2 +- 3 files changed, 101 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ac35a53..5335eacf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Improved the style of the symbol search component - Extended the users table in the admin control panel - Increased the default request timeout (`REQUEST_TIMEOUT`) +- Upgraded `cheerio` from version `1.0.0-rc.12` to `1.0.0` ## 2.124.1 - 2024-11-25 diff --git a/package-lock.json b/package-lock.json index c6720ef9..6066d16e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -56,7 +56,7 @@ "chartjs-chart-treemap": "2.3.1", "chartjs-plugin-annotation": "2.1.2", "chartjs-plugin-datalabels": "2.2.0", - "cheerio": "1.0.0-rc.12", + "cheerio": "1.0.0", "class-transformer": "0.5.1", "class-validator": "0.14.1", "color": "4.2.3", @@ -14281,21 +14281,25 @@ } }, "node_modules/cheerio": { - "version": "1.0.0-rc.12", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", - "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0.tgz", + "integrity": "sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==", "license": "MIT", "dependencies": { "cheerio-select": "^2.1.0", "dom-serializer": "^2.0.0", "domhandler": "^5.0.3", - "domutils": "^3.0.1", - "htmlparser2": "^8.0.1", - "parse5": "^7.0.0", - "parse5-htmlparser2-tree-adapter": "^7.0.0" + "domutils": "^3.1.0", + "encoding-sniffer": "^0.2.0", + "htmlparser2": "^9.1.0", + "parse5": "^7.1.2", + "parse5-htmlparser2-tree-adapter": "^7.0.0", + "parse5-parser-stream": "^7.1.2", + "undici": "^6.19.5", + "whatwg-mimetype": "^4.0.0" }, "engines": { - "node": ">= 6" + "node": ">=18.17" }, "funding": { "url": "https://github.com/cheeriojs/cheerio?sponsor=1" @@ -14318,6 +14322,34 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/cheerio/node_modules/htmlparser2": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz", + "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.1.0", + "entities": "^4.5.0" + } + }, + "node_modules/cheerio/node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -17291,6 +17323,43 @@ "iconv-lite": "^0.6.2" } }, + "node_modules/encoding-sniffer": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.0.tgz", + "integrity": "sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg==", + "license": "MIT", + "dependencies": { + "iconv-lite": "^0.6.3", + "whatwg-encoding": "^3.1.1" + }, + "funding": { + "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" + } + }, + "node_modules/encoding-sniffer/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/encoding-sniffer/node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/encoding/node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -28271,6 +28340,18 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, + "node_modules/parse5-parser-stream": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", + "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", + "license": "MIT", + "dependencies": { + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/parse5-sax-parser": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/parse5-sax-parser/-/parse5-sax-parser-7.0.0.tgz", @@ -33272,6 +33353,15 @@ "integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==", "license": "MIT" }, + "node_modules/undici": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.0.tgz", + "integrity": "sha512-BUgJXc752Kou3oOIuU1i+yZZypyZRqNPW0vqoMPl8VaoalSfeR0D8/t4iAS3yirs79SSMTxTag+ZC86uswv+Cw==", + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", diff --git a/package.json b/package.json index 9332f33e..8d9624de 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,7 @@ "chartjs-chart-treemap": "2.3.1", "chartjs-plugin-annotation": "2.1.2", "chartjs-plugin-datalabels": "2.2.0", - "cheerio": "1.0.0-rc.12", + "cheerio": "1.0.0", "class-transformer": "0.5.1", "class-validator": "0.14.1", "color": "4.2.3", From 66bbbc2cb81d8b313b9d16d86348fb86dc6ce902 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Fri, 29 Nov 2024 20:21:46 +0100 Subject: [PATCH 54/65] Feature/improve error handling in Ghostfolio data provider (#4079) * Improve error handling --- .../ghostfolio/ghostfolio.service.ts | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/apps/api/src/services/data-provider/ghostfolio/ghostfolio.service.ts b/apps/api/src/services/data-provider/ghostfolio/ghostfolio.service.ts index acd66b0a..5be7d831 100644 --- a/apps/api/src/services/data-provider/ghostfolio/ghostfolio.service.ts +++ b/apps/api/src/services/data-provider/ghostfolio/ghostfolio.service.ts @@ -28,6 +28,7 @@ import { Injectable, Logger } from '@nestjs/common'; import { DataSource, SymbolProfile } from '@prisma/client'; import { format } from 'date-fns'; import got from 'got'; +import { StatusCodes } from 'http-status-codes'; @Injectable() export class GhostfolioService implements DataProviderInterface { @@ -106,6 +107,17 @@ export class GhostfolioService implements DataProviderInterface { [symbol]: historicalData }; } catch (error) { + let message = error; + + if (error.response?.statusCode === StatusCodes.TOO_MANY_REQUESTS) { + message = 'RequestError: The daily request limit has been exceeded'; + } else if (error.response?.statusCode === StatusCodes.UNAUTHORIZED) { + message = + 'RequestError: The provided API key is invalid. Please update it in the Settings section of the Admin Control panel.'; + } + + Logger.error(message, 'GhostfolioService'); + throw new Error( `Could not get historical market data for ${symbol} (${this.getName()}) from ${format( from, @@ -159,6 +171,11 @@ export class GhostfolioService implements DataProviderInterface { message = `RequestError: The operation to get the quotes was aborted because the request to the data provider took more than ${( this.configurationService.get('REQUEST_TIMEOUT') / 1000 ).toFixed(3)} seconds`; + } else if (error.response?.statusCode === StatusCodes.TOO_MANY_REQUESTS) { + message = 'RequestError: The daily request limit has been exceeded'; + } else if (error.response?.statusCode === StatusCodes.UNAUTHORIZED) { + message = + 'RequestError: The provided API key is invalid. Please update it in the Settings section of the Admin Control panel.'; } Logger.error(message, 'GhostfolioService'); @@ -196,6 +213,11 @@ export class GhostfolioService implements DataProviderInterface { message = `RequestError: The operation to search for ${query} was aborted because the request to the data provider took more than ${( this.configurationService.get('REQUEST_TIMEOUT') / 1000 ).toFixed(3)} seconds`; + } else if (error.response?.statusCode === StatusCodes.TOO_MANY_REQUESTS) { + message = 'RequestError: The daily request limit has been exceeded'; + } else if (error.response?.statusCode === StatusCodes.UNAUTHORIZED) { + message = + 'RequestError: The provided API key is invalid. Please update it in the Settings section of the Admin Control panel.'; } Logger.error(message, 'GhostfolioService'); From c6525ec0f449bdafbf4fb2f576b573b2e7909ca3 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Fri, 29 Nov 2024 21:08:19 +0100 Subject: [PATCH 55/65] Feature/refresh cryptocurrencies list 20241127 (#4077) * Update cryptocurrencies.json * Update changelog --- CHANGELOG.md | 1 + .../cryptocurrencies/cryptocurrencies.json | 2317 ++++++++++++++++- 2 files changed, 2188 insertions(+), 130 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5335eacf..9d9ac3d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Improved the style of the symbol search component - Extended the users table in the admin control panel +- Refreshed the cryptocurrencies list - Increased the default request timeout (`REQUEST_TIMEOUT`) - Upgraded `cheerio` from version `1.0.0-rc.12` to `1.0.0` diff --git a/apps/api/src/assets/cryptocurrencies/cryptocurrencies.json b/apps/api/src/assets/cryptocurrencies/cryptocurrencies.json index c63ba5c7..ac215738 100644 --- a/apps/api/src/assets/cryptocurrencies/cryptocurrencies.json +++ b/apps/api/src/assets/cryptocurrencies/cryptocurrencies.json @@ -1,6 +1,8 @@ { + "3": "The Three Musketeers", "7": "Lucky7", "42": "42 Coin", + "47": "President Trump", "300": "300 token", "365": "365Coin", "369": "Nikola Tesla Token", @@ -18,14 +20,15 @@ "2192": "LERNITAS", "$MAID": "MaidCoin", "$ROPE": "Rope", - "$TIME": "Madagascar Token", "$TREAM": "World Stream Finance", "00": "ZER0ZER0", "007": "007 coin", + "0DOG": "Bitcoin Dogs", "0KN": "0 Knowledge Network", "0NE": "Stone", "0X1": "0x1.tools: AI Multi-tool Plaform", "0XBTC": "0xBitcoin", + "0XCOCO": "0xCoco", "0XDEV": "DEVAI", "0XG": "0xGpu.ai", "0XGAS": "0xGasless", @@ -62,6 +65,7 @@ "1UP": "Uptrennd", "1WO": "1World", "2022M": "2022MOON", + "21BTC": "21.co Wrapped BTC", "21X": "21X", "2BACCO": "2BACCO Coin", "2BASED": "2Based Finance", @@ -81,6 +85,7 @@ "32BIT": "32Bitcoin", "360NS": "360 NOSCOPE INSTASWAP WALLBANG", "37C": "37Protocol", + "3AC": "THREE ARROWZ CAPITEL", "3AIR": "3air", "3CEO": "FLOKI SHIBA PEPE CEO", "3CRV": "LP 3pool Curve", @@ -90,6 +95,7 @@ "3KM": "3 Kingdoms Multiverse", "3P": "Web3Camp", "3ULL": "3ULL Coin", + "3ULLV1": "Playa3ull Games v1", "3XD": "3DChain", "404A": "404Aliens", "404BLOCKS": "404Blocks", @@ -97,12 +103,16 @@ "4ART": "4ART Coin", "4CHAN": "4Chan", "4CZ": "FourCZ", + "4DOGE": "4DOGE", "4JNET": "4JNET", "4MW": "For Meta World", + "4P": "4P FOUR", "4RZ": "4REALZA COIN", "4TOKEN": "Ignore Fud", + "4WIN": "4TRUMP", "4WMM": "4-Way Mirror Money", "50C": "50Cent", + "50TRUMP": "50TRUMP", "50X": "50x.com", "5IRE": "5ire", "77G": "GraphenTech", @@ -112,15 +122,18 @@ "8BT": "8 Circuit Studios", "8PAY": "8Pay", "8X8": "8X8 Protocol", + "99BTC": "99 Bitcoins", "9DOGS": "NINE DOGS", "9GAG": "9GAG", "9MM": "Shigure UI", "A": "Alpha Token", + "A1INCH": "1inch (Arbitrum Bridge)", "A2A": "A2A", "A4": "A4 Finance", "A4M": "AlienForm", "A51": "A51 Finance", "A5T": "Alpha5", + "A8": "Ancient8", "AA": "Alva", "AAA": "Moon Rabbit", "AAB": "AAX Token", @@ -129,29 +142,44 @@ "AAG": "AAG Ventures", "AAI": "AutoAir AI", "AAPX": "AMPnet", + "AARDY": "Baby Aardvark", + "AARK": "Aark", "AART": "ALL.ART", "AAST": "AASToken", "AAT": "Agricultural Trade Chain", "AAVE": "Aave", + "AAVEE": "AAVE.e (Avalanche Bride)", + "AAVEGOTCHIFOMO": "Aavegotchi FOMO", + "AAX": "Academic Labs", "AAZ": "ATLAZ", + "AB1INCH": "1inch (Avalanche Bride)", "ABA": "EcoBall", "ABBC": "ABBC Coin", "ABC": "ABC Chain", "ABCC": "ABCC Token", "ABCD": "Crypto Inu", + "ABCM": "ABCMETA", + "ABCRV": "Curve DAO Token (Arbitrum Bridge)", "ABD": "AB DEFI", + "ABDS": "ABDS Token", + "ABE": "ABE", "ABEL": "Abelian", "ABET": "Altbet", "ABEY": "Abey", "ABIC": "Arabic", "ABJ": "Abjcoin", "ABL": "Airbloc", + "ABLE": "Able Finance", + "ABLINK": "Chainlink (Arbitrum Bridge)", "ABN": "Antofy", "ABO": "Albino", "ABOND": "ApeBond", "ABONDV1": "ApeSwap", "ABR": "Allbridge", "ABT": "ArcBlock", + "ABUL": "Abulaba", + "ABUNI": "Uniswap Protocol Token (Arbitrum Bridge)", + "ABUSDC": "USD Coin (Arbitrum Bridge)", "ABX": "Arbidex", "ABY": "ArtByte", "ABYSS": "Abyss Finance", @@ -167,14 +195,17 @@ "ACDC": "Volt", "ACE": "Fusionist", "ACEN": "Acent", + "ACEO": "Ace of Pentacles", "ACES": "AcesCoin", "ACET": "Acet", "ACETH": "Acether", "ACH": "Alchemy Pay", + "ACHAIN": "Achain", "ACHC": "AchieveCoin", "ACHI": "achi", "ACID": "AcidCoin", "ACK": "Arcade Kingdoms", + "ACL": "Auction Light", "ACM": "AC Milan Fan Token", "ACN": "AvonCoin", "ACOIN": "ACoin", @@ -184,7 +215,8 @@ "ACRE": "Arable Protocol", "ACRIA": "Acria.AI", "ACS": "Access Protocol", - "ACT": "Achain", + "ACSI": "ACryptoSI", + "ACT": "Act I The AI Prophecy", "ACTA": "Acta Finance", "ACTIN": "Actinium", "ACTN": "Action Coin", @@ -197,9 +229,12 @@ "ADAB": "Adab Solutions", "ADACASH": "ADACash", "ADAI": "Aave DAI", + "ADAM": "Adam Back", "ADANA": "Adanaspor Fan Token", "ADAO": "ADADao", "ADAPAD": "ADAPad", + "ADASOL": "ADA", + "ADASTRA": "Ad Astra", "ADAT": "Adadex Tools", "ADAX": "ADAX", "ADB": "Adbank", @@ -208,6 +243,7 @@ "ADD": "ADD.xyz", "ADDAMS": "ADDAMS AI", "ADDY": "Adamant", + "ADE": "AADex Finance", "ADEL": "Akropolis Delphi", "ADF": "Art de Finance", "ADH": "Adhive", @@ -218,6 +254,7 @@ "ADN": "Aladdin", "ADNT": "Aiden", "ADO": "ADO Protocol", + "ADOG": "America Dog", "ADOGE": "Arbidoge", "ADON": "Adonis", "ADP": "Adappter Token", @@ -270,6 +307,7 @@ "AFR": "Afreum", "AFRO": "Afrostar", "AFROX": "AfroDex", + "AFSUI": "Aftermath Staked SUI", "AFTT": "Africa Trading Chain", "AFX": "Afrix", "AFYON": "Afyonspor Fan Token", @@ -278,12 +316,16 @@ "AGA": "AGA Token", "AGATA": "Agatech", "AGB": "Apes Go Bananas", + "AGC": "Argocoin", + "AGENT": "AgentLayer", "AGET": "Agetron", + "AGETH": "Kelp Gain", "AGEUR": "agEUR", "AGF": "Augmented Finance", "AGG": "AGG", "AGI": "Delysium", "AGII": "AGII", + "AGIL": "Agility LSD", "AGIV1": "SingularityNET v1", "AGIX": "SingularityNET", "AGLA": "Angola", @@ -298,6 +340,7 @@ "AGRS": "Agoras Token", "AGS": "Aegis", "AGT": "aGifttoken", + "AGURI": "Aguri-Chan", "AGV": "Astra Guild Ventures", "AGVC": "AgaveCoin", "AGVE": "Agave", @@ -305,6 +348,7 @@ "AHOO": "Ahoolee", "AHT": "AhaToken", "AI": "Sleepless", + "AI16Z": "ai16z", "AIA": "AIA Chain", "AIAI": "All In AI", "AIAKITA": "AiAkita", @@ -316,11 +360,13 @@ "AIBK": "AIB Utility Token", "AIBU": "AIBUZZ TOKEN", "AIC": "AI Crypto", + "AICELL": "AICell", "AICH": "AIChain", "AICO": "AICON", "AICODE": "AI CODE", "AICORE": "AICORE", "AID": "AidCoin", + "AIDA": "Ai-Da robot", "AIDI": "Aidi Inu", "AIDOC": "AI Doctor", "AIDOG": "AiDoge", @@ -333,6 +379,7 @@ "AIEN": "AIENGLISH", "AIF": "AI FREEDOM TOKEN", "AIFLOKI": "AI Floki", + "AIFUN": "AI Agent Layer", "AIG": "A.I Genesis", "AIGPU": "AIGPU Token", "AII": "Artificial Idiot", @@ -340,13 +387,16 @@ "AIKEK": "AlphaKEK.AI", "AILINK": "AiLink Token", "AIM": "ModiHost", + "AIMAGA": "Presidentexe", "AIMARKET": "Acria.AI AIMARKET", "AIMBOT": "AimBot AI", "AIMEE": "AIMEE", + "AIMET": "AI Metaverse", "AIMR": "MeromAI", "AIMS": "HighCastle Token", "AIMX": "Aimedis", "AIN": "AI Network", + "AINA": "Ainastasia", "AINN": "AINN", "AINU": "Ainu Token", "AION": "Aion", @@ -358,11 +408,14 @@ "AIPEPE": "AI PEPE KING", "AIPG": "AI Power Grid", "AIPIN": "AI PIN", + "AIPO": "Aipocalypto", "AIR": "Altair", "AIRB": "BillionAir", "AIRBTC": "AIRBTC", + "AIRDROP": "AIRDROP2049", "AIRE": "Tokenaire", "AIRI": "aiRight", + "AIRIAN": "AIRian", "AIRT": "Aircraft", "AIRTNT": "Tenti", "AIRTOKEN": "AirToken", @@ -370,6 +423,7 @@ "AIS": "AISwap", "AISCII": "AISCII", "AISHIB": "ARBSHIB", + "AIST": "Artificial intelligence staking token", "AIT": "AIT Protocol", "AITECH": "Artificial Intelligence Utility Token", "AITEK": "AI Technology", @@ -382,22 +436,32 @@ "AIUS": "Arbius", "AIWALLET": "AiWallet Token", "AIX": "Aigang", + "AIXBT": "aixbt by Virtuals", + "AIXERC": "AI-X", "AJNA": "Ajna Protocol", + "AJUN": "Ajuna Network", "AK12": "AK12", "AKA": "Akroma", "AKI": "Aki Network", + "AKIT": "Akita Inu", "AKITA": "Akita Inu", + "AKITAI": "AKITA INU", "AKITAX": "Akitavax", "AKN": "Akoin", "AKNC": "Aave KNC v1", + "AKOBI": "AKOBI", "AKREP": "Antalyaspor Token", "AKRO": "Akropolis", "AKT": "Akash Network", "AKTIO": "AKTIO Coin", - "ALA": "ALA", + "AL": "ArchLoot", + "ALA": "Alanyaspor Fan Token", "ALAN": "Alan the Alien", + "ALASKA": "Alaska", + "ALATOKEN": "ALA", "ALB": "Alien Base", "ALBART": "Albärt", + "ALBE": "ALBETROS", "ALBEDO": "ALBEDO", "ALBT": "AllianceBlock", "ALC": "Arab League Coin", @@ -409,6 +473,7 @@ "ALD": "AladdinDAO", "ALDIN": "Alaaddin.ai", "ALE": "Ailey", + "ALEO": "ALEO", "ALEPH": "Aleph.im", "ALEX": "ALEX Lab", "ALEXANDRITE": "Alexandrite", @@ -423,7 +488,9 @@ "ALIAS": "Alias", "ALIC": "AliCoin", "ALICE": "My Neighbor Alice", + "ALICEA": "Alice AI", "ALIEN": "AlienCoin", + "ALIENPEP": "Alien Pepe", "ALIF": " ALIF COIN", "ALINK": "Aave LINK v1", "ALIS": "ALISmedia", @@ -432,22 +499,29 @@ "ALIX": "AlinX", "ALKI": "Alkimi", "ALLBI": "ALL BEST ICO", + "ALLC": "All Crypto Mechanics", "ALLEY": "NFT Alley", "ALLIN": "All in", "ALM": "Alium Finance", + "ALMAN": "Alman", "ALMC": "Awkward Look Monkey Club", "ALME": "Alita", "ALN": "Aluna", + "ALNV1": "Aluna v1", "ALOHA": "Aloha", "ALOT": "Dexalot", "ALP": "Alphacon", "ALPA": "Alpaca", "ALPACA": "Alpaca Finance", + "ALPACAS": "Bitcoin Mascot", "ALPH": "Alephium", "ALPHA": "Alpha Finance Lab", "ALPHAAI": "Alpha AI", "ALPHABET": "Alphabet", "ALPHAC": "Alpha Coin", + "ALPHAF": "Alpha Fi", + "ALPHAG": "Alpha Gardeners", + "ALPHAPETTO": "Alpha Petto Shells", "ALPHAS": "Alpha Shards", "ALPHR": "Alphr", "ALPINE": "Alpine F1 Team Fan Token", @@ -465,6 +539,7 @@ "ALUSD": "Alchemix USD", "ALUX": "Alux Bank", "ALV": "Allive", + "ALV1": "ArchLoot v1", "ALVA": "Alvara Protocol", "ALWAYS": "Always Evolving", "ALX": "ALAX", @@ -484,6 +559,9 @@ "AMDC": "Allmedi Coin", "AMDG": "AMDG", "AME": "Amepay", + "AMER": "America", + "AMERI": "AMERICAN EAGLE", + "AMERIC": "American True Hero", "AMERICA": "America", "AMERICANCOIN": "AmericanCoin", "AMF": "AddMeFast", @@ -514,6 +592,7 @@ "ANAL": "AnalCoin", "ANALOS": "analoS", "ANALY": "Analysoor", + "ANAT": "Anatolia Token", "ANB": "Angryb", "ANC": "Anchor Protocol", "ANCHOR": "AnchorSwap", @@ -526,15 +605,21 @@ "ANDWU": "Chinese Andy", "ANDX": "Arrano", "ANDY": "ANDY", + "ANDYB": "AndyBlast", + "ANDYBNB": "Andy", + "ANDYBSC": "ANDY", + "ANDYMAN": "ANDYMAN", + "ANDYSOL": "Andy on SOL", "ANGEL": "Crypto Angel", "ANGL": "Angel Token", "ANGLE": "ANGLE", "ANGO": "Aureus Nummus Gold", "ANGRYSLERF": "ANGRYSLERF", - "ANI": "Animecoin", + "ANI": "Anime Token", "ANIM": "Animalia", "ANIMA": "Realm Anima", "ANIME": "Anime", + "ANIMECOIN": "Animecoin", "ANJ": "Aragon Court", "ANJI": "Anji", "ANK": "AlphaLink", @@ -551,6 +636,7 @@ "ANONCOIN": "Anoncoin", "ANRX": "AnRKey X", "ANS": "ANS Crypto Coin", + "ANSOM": "Ansom", "ANSR": "Answerly", "ANT": "Aragon", "ANTC": "AntiLitecoin", @@ -559,11 +645,14 @@ "ANTI": "Anti Bitcoin", "ANTIS": "Antis Inu", "ANTS": "ANTS Reloaded", + "ANTT": "Antara Token", "ANUS": "URANUS", "ANV": "Aniverse", + "ANVL": "Anvil", "ANW": "Anchor Neural World", "ANY": "Anyswap", "ANYONE": "ANyONe Protocol", + "ANZENUSD": "Anzen Finance", "AOC": "Alickshundra Occasional-Cortex", "AOG": "AgeOfGods", "AOK": "AOK", @@ -576,12 +665,15 @@ "APCG": "ALLPAYCOIN", "APD": "Aptopad", "APE": "ApeCoin", - "APECOIN": "Asia Pacific Electronic Coin", "APED": "Baddest Alpha Ape Bundle", - "APES": "Alpha Petto Shells", + "APEDEV": "The dev is an Ape", + "APEFUN": "Ape", + "APEPE": "Ape and Pepe", + "APES": "APES", "APETARDIO": "Apetardio", "APEWIFHAT": "ApeWifHat", "APEX": "ApeX Protocol", + "APEXA": "Apex AI", "APEXCOIN": "ApexCoin", "APEXT": "ApexToken", "APFC": "APF coin", @@ -594,11 +686,15 @@ "APL": "Apollo Currency", "APM": "apM Coin", "APN": "Apron", + "APO": "Apollo Caps ETF", "APOD": "AirPod", + "APOL": "Apollo FTW", + "APOLL": "Apollon Limassol", "APOLLO": "Apollo Crypto", "APP": "Moon App", "APPA": "Dappad", "APPC": "AppCoins", + "APPEALUSD": "Appeal dollar", "APPLE": "AppleSwap", "APRICOT": "Apricot Finance", "APRIL": "April", @@ -626,6 +722,7 @@ "AQUAC": "Aquachain", "AQUACITY": "Aquacity", "AQUAGOAT": "Aqua Goat", + "AQUAGOATV1": "Aqua Goat v1", "AQUAP": "Planet Finance", "AQUARI": "Aquari", "AR": "Arweave", @@ -635,14 +732,17 @@ "ARB": "Arbitrum", "ARBI": "Arbi", "ARBIT": "Arbit Coin", + "ARBP": "ARB Protocol", "ARBS": "Arbswap", "ARBT": "ARBITRAGE", "ARBUZ": "ARBUZ", - "ARC": "ArcticCoin", - "ARCA": "Arca", + "ARC": "Neko Arc", + "ARCA": "Legend of Arcadia", "ARCAD": "Arcadeum", "ARCADE": "ARCADE", + "ARCADECITY": "Arcade City", "ARCADEF": "arcadefi", + "ARCADEN": "ArcadeNetwork", "ARCANE": "Arcane Token", "ARCAS": "Arcas", "ARCH": "Archway", @@ -650,10 +750,10 @@ "ARCHCOIN": "ArchCoin", "ARCHE": "Archean", "ARCHIVE": "Chainback", - "ARCHL": "ArchLoot", "ARCO": "AquariusCoin", "ARCONA": "Arcona", "ARCT": "ArbitrageCT", + "ARCTICCOIN": "ArcticCoin", "ARCX": "ARC Governance", "ARDR": "Ardor", "ARDX": "ArdCoin", @@ -672,6 +772,7 @@ "ARI10": "Ari10", "ARIA": "Legends of Aria", "ARIA20": "Arianee", + "ARIT": "ArithFi", "ARIX": "Arix", "ARK": "ARK", "ARKEN": "Arken Finance", @@ -680,12 +781,15 @@ "ARKM": "Arkham", "ARKN": "Ark Rivals", "ARKY": "Arky", + "ARKYS": "Arky Satoshi's Dog", "ARM": "Armory Coin", "ARMA": "Aarma", "ARMOR": "ARMOR", "ARMR": "ARMR", "ARMS": "2Acoin", + "ARMY": "Army of Fortune Coin", "ARNA": "ARNA Panacea", + "ARNC": "Arnoya classic", "ARNM": "Arenum", "ARNO": "ARNO", "ARNX": "Aeron", @@ -715,9 +819,11 @@ "ARTG": "Goya Giant Token", "ARTH": "ARTH", "ARTI": "Arti Project", + "ARTIF": "Artificial Intelligence", "ARTII": "ARTII Token", "ARTL": "ARTL", "ARTM": "ARTM", + "ARTMETIS": "Staked Metis Token", "ARTP": "ArtPro", "ARTR": "Artery Network", "ARTT": "ARTT Network", @@ -738,6 +844,7 @@ "ASG": "Asgard", "ASGC": "ASG", "ASH": "ASH", + "ASHS": "AshSwap", "ASI": "Artificial Superintelligence Alliance", "ASIA": "Asia Coin", "ASIMI": "ASIMI", @@ -745,10 +852,12 @@ "ASK": "Permission Coin", "ASKO": "Asko", "ASM": "Assemble Protocol", + "ASMO": "AS Monaco Fan Token", "ASN": "Ascension Coin", "ASNT": "Assent Protocol", "ASP": "Aspire", "ASPC": "Astropup Coin", + "ASPIRIN": "Aspirin", "ASPO": "ASPO Shards", "ASQT": "ASQ Protocol", "ASR": "AS Roma Fan Token", @@ -767,13 +876,16 @@ "ASTRAFER": "Astrafer", "ASTRAFERV1": "Astrafer v1", "ASTRAL": "Astral", - "ASTRO": "AstroSwap", + "ASTRO": "Astroport", "ASTROC": "Astroport Classic", "ASTROLION": "AstroLion", "ASTRONAUT": "Astronaut", + "ASTROO": "Astroon", "ASTROP": "AstroPepeX", + "ASTROS": "AstroSwap", "ASTX": "Asterix Labs", "ASUNA": "Asuna Hentai", + "ASUSHI": "Sushi (Arbitrum Bridge)", "ASVA": "Asva", "ASW": "AdaSwap", "ASY": "ASYAGRO", @@ -790,19 +902,23 @@ "ATFI": "Atlantic Finance Token", "ATFS": "ATFS Project", "ATH": "Aethir", + "ATHCAT": "ATH CAT", "ATHE": "Atheios", "ATHEN": "Athenas AI", + "ATHENA": "Athena DexFi", "ATHVODKA": "All Time High Vodka", "ATID": "AstridDAO Token", "ATK": "Attack Wagon", "ATKN": "A-Token", "ATL": "ATLANT", + "ATLA": "Atleta Network", "ATLAS": "Star Atlas", "ATLX": "Atlantis Loans Polygon", "ATM": "Atletico de Madrid Fan Token", "ATMA": "ATMA", "ATMC": "Autumncoin", "ATMCHAIN": "ATMChain", + "ATMCOIN": "ATM", "ATMI": "Atonomi", "ATMOS": "Novusphere", "ATN": "ATN", @@ -820,6 +936,7 @@ "ATRI": "Atari Token", "ATRNO": "AETERNUS", "ATROFA": "Atrofarm", + "ATRS": "Attarius Network", "ATS": "Atlas DEX", "ATT": "Attila", "ATTR": "Attrace", @@ -835,6 +952,7 @@ "AUDT": "Auditchain", "AUDX": "eToro Australian Dollar", "AUK": "Aukcecoin", + "AUKI": "Auki Labs", "AUN": "Authoreon", "AUNIT": "Aunit", "AUPC": "Authpaper", @@ -850,19 +968,21 @@ "AURUM": "Aurum", "AURY": "Aurory", "AUSCM": "Auric Network", - "AUSD": "Appeal dollar", + "AUSD": "AUSD", "AUSDC": "Aave USDC v1", "AUSDT": "aUSDT", "AUT": "Autoria", "AUTHORSHIP": "Authorship", "AUTISM": "AUTISM", "AUTO": "Auto", - "AUTON": "Autonio", "AUTUMN": "Autumn", + "AUVERSE": "AuroraVerse", "AUX": "Auxilium", "AV": "Avatar Coin", "AVA": "Travala", + "AVACN": "AVACOIN", "AVAI": "Orca AVAI", + "AVAIL": "Avail", "AVAL": "Avaluse", "AVALON": "Avalon", "AVALOX": "AVALOX", @@ -873,8 +993,11 @@ "AVAV": "AVAV", "AVAX": "Avalanche", "AVAXIOU": "Avalanche IOU", + "AVB": "Autonomous Virtual Beings", "AVDO": "AvocadoCoin", "AVE": "Avesta", + "AVEN": "Aventis AI", + "AVENT": "Aventa", "AVG": "Avocado DAO", "AVH": "Animation Vision Cash", "AVI": "Aviator", @@ -921,6 +1044,8 @@ "AXN": "Axion", "AXNT": "Axentro", "AXO": "Axo", + "AXOL": "Axol", + "AXON": "AxonDAO Governance Token", "AXPR": "aXpire", "AXR": "AXRON", "AXS": "Axie Infinity Shards", @@ -962,6 +1087,7 @@ "BABY": "BabySwap", "BABYANDY": "Baby Andy", "BABYB": "Baby Bali", + "BABYBI": "Baby Bitcoin", "BABYBINANCE": "BABYBINANCE", "BABYBITC": "BabyBitcoin", "BABYBNB": "BabyBNB", @@ -972,12 +1098,17 @@ "BABYBOMEOW": "Baby of BOMEOW", "BABYBONK": "Baby Bonk", "BABYBTC": "BABYBTC", + "BABYC": "Baby Cat", "BABYCAT": "Baby Cat Coin", + "BABYCATE": "BabyCate", "BABYCATS": "Baby Cat Coin", "BABYCEO": "Baby Doge CEO", "BABYCRASH": "BabyCrash", "BABYCRAZYT": "BABY CRAZY TIGER", "BABYCUBAN": "Baby Cuban", + "BABYCZHAO": "Baby Czhao", + "BABYD": "Baby Dragon", + "BABYDENG": "Baby Moo Deng", "BABYDOGE": "BabyDoge", "BABYDOGEINU": "BABY DOGE INU", "BABYDOGEZILLA": "BabyDogeZilla", @@ -986,10 +1117,14 @@ "BABYFB": "Baby Floki Billionaire", "BABYFLOKI": "BabyFloki", "BABYFLOKIZILLA": "BabyFlokiZilla", + "BABYG": "BabyGME", "BABYGME": "Baby GameStop", + "BABYGOAT": "Baby Goat", "BABYGOLDEN": "Baby Golden Coin", "BABYGROK": "Baby Grok", "BABYGUMMY": "BABY GUMMY", + "BABYHARRIS": "Baby Harris", + "BABYHIPPO": "BABY HIPPO", "BABYHKTIGER": "BabyHkTiger", "BABYHONK": "Baby Honk", "BABYJERRY": "Baby Jerry", @@ -997,19 +1132,31 @@ "BABYKABOSU": "Baby Kabosu", "BABYKITTY": "BabyKitty", "BABYLONG": "Baby Long", + "BABYM": "BabyMAGA", "BABYMAGA": "Baby Maga", + "BABYME": "Baby Meme Coin", "BABYMEME": "Baby Memecoin", + "BABYMIGGLES": "Baby Miggles", + "BABYMO": "Baby Moon Floki", + "BABYMU": "Baby Musk", "BABYMUSK": "Baby Musk", "BABYMYRO": "Babymyro", + "BABYNEIRO": "Baby Neiro", "BABYOKX": "BABYOKX", + "BABYP": "BabyPepe", + "BABYPEIPEI": "Baby PeiPei", "BABYPEPE": "Babypepe (BSC)", + "BABYPNUT": "Baby Pnut", + "BABYPOPCAT": "Baby PopCat", "BABYPORK": "Baby Pepe Fork", "BABYRATS": "Baby Rats", "BABYRWA": "BabyRWA", + "BABYS": "Baby Slerf", "BABYSAITAMA": "Baby Saitama", "BABYSHARK": "Baby Shark", "BABYSHIB": "Baby Shiba Inu", "BABYSHIBAINU": "Baby Shiba Inu", + "BABYSHIRO": "Baby Shiro Neko", "BABYSHIV": "Baby Shiva", "BABYSLERF": "BabySlerf", "BABYSOL": "Baby Solana", @@ -1019,15 +1166,19 @@ "BABYTK": "Baby Tiger King", "BABYTOMCAT": "Baby Tomcat", "BABYTOSHI": "Baby Toshi", + "BABYTR": "BABYTRUMP", "BABYTROLL": "Baby Troll", "BABYTRUMP": "BABYTRUMP", "BABYWIF": "babydogwifhat", + "BABYWLFI": "Baby WLFI", "BABYX": "Baby X", "BAC": "Basis Cash", "BACK": "DollarBack", "BACOIN": "BACoin", "BACON": "BaconDAO (BACON)", "BAD": "Bad Idea AI", + "BADA": "Bad Alien Division", + "BADC": "BADCAT", "BADCAT": "Andy’s Alter Ego", "BADGER": "Badger DAO", "BAFC": "BabyApeFunClub", @@ -1041,32 +1192,46 @@ "BAKAC": "Baka Casino", "BAKE": "BakeryToken", "BAKED": "Baked", + "BAKEDB": "Baked Beans Token", + "BAKEDTOKEN": "Baked", + "BAKSO": "Disney Sumatran Tiger", "BAKT": "Backed Protocol", "BAL": "Balancer", "BALA": "Shambala", "BALD": "Bald", "BALIN": "Balin Bank", + "BALL": "Game 5 BALL", "BALLZ": "Wolf Wif", + "BALN": "Balanced", "BALPHA": "bAlpha", "BALT": "Brett's cat", "BALTO": "Balto Token", "BAMA": "BabyAMA", "BAMBIT": "BAMBIT", "BAMBOO": "BambooDeFi", + "BAMITCOIN": "Bamit", "BAN": "Banano", "BANANA": "Banana Gun", + "BANANAF": "Banana For Scale", "BANANAS": "Monkey Peepo", "BANC": "Babes and Nerds", "BANCA": "BANCA", "BAND": "Band Protocol", "BANDEX": "Banana Index", + "BANDIT": "Bandit on Base", "BANG": "BANG", + "BANGY": "BANGY", "BANK": "Float Protocol", + "BANKA": "Bank AI", + "BANKC": "Bankcoin", + "BANKER": "BankerCoinAda", "BANKETH": "BankEth", + "BANKSY": "BANKSY", "BANNER": "BannerCoin", "BANUS": "Banus.Finance", "BANX": "Banx.gg", "BAO": "Bao Finance", + "BAOBAO": "BaoBao", "BAOE": "Business Age of Empires", "BAOM": "Battle of Memes", "BAOS": "BaoBaoSol", @@ -1074,15 +1239,33 @@ "BARA": "Capybara", "BARC": "The Blu Arctic Water Company", "BAREBEARS": "BAREBEARS", + "BARIO": "Bario", "BARK": "Bored Ark", "BARRON": "Time Traveler", + "BARS": "Banksters Token", + "BARSIK": "Hasbulla's Cat", "BART": "BarterTrade", + "BARTKRC": "BART Token", + "BARY": "Bary", "BAS": "Basis Share", - "BASE": "Base Protocol", "BASEAI": "BaseAI", + "BASECAT": "BASE CAT", + "BASECOIN": "BASECOIN", "BASED": "Based Money", "BASEDAI": "BasedAI", + "BASEDCHILL": "Based Chill Guy", + "BASEDCOPE": "COPE", + "BASEDFINANCE": "Based", + "BASEDHOPPY": "Based Hoppy (basedhoppy.vip)", + "BASEDP": "Based Pepe", + "BASEDR": "Based Rabbit", + "BASEDS": "BasedSwap", + "BASEDV1": "Based Money v1", "BASEHEROES": "Baseheroes", + "BASEPROTOCOL": "Base Protocol", + "BASESWAPX": "BaseX", + "BASEVE": "Base Velocimeter", + "BASEX": "Base Terminal", "BASH": "LuckChain", "BASHC": "BashCoin", "BASHOS": "Bashoswap", @@ -1094,6 +1277,8 @@ "BASTET": "Bastet Goddess", "BAT": "Basic Attention Token", "BATH": "Battle Hero", + "BATMAN": "BATMAN", + "BATO": "Batonex Token", "BATS": "Batcoin", "BAVA": "Baklava", "BAX": "BABB", @@ -1109,6 +1294,7 @@ "BBC": "Bull BTC Club", "BBCC": "BaseballCardCoin", "BBCG": "BBC Gold Coin", + "BBCH": "Binance Wrapped BCH", "BBCT": "TraDove B2BCoin", "BBDC": "Block Beats Network", "BBDT": "BBD Token", @@ -1122,19 +1308,24 @@ "BBL": "beoble", "BBN": "BBNCOIN", "BBO": "Bigbom", + "BBONK": "BitBonk", "BBOS": "Blackbox Foundation", "BBP": "BiblePay", "BBR": "Boolberry", "BBRETT": "Baby Brett", "BBS": "BBSCoin", + "BBSOL": "Bybit Staked SOL", "BBT": "BitBook", "BBTC": "Binance Wrapped BTC", "BBTF": "Block Buster Tech Inc", "BBUSD": "BounceBit USD", + "BBYDEV": "The Dev is a Baby", "BC": "Bitcoin Confidential", "BCA": "Bitcoin Atom", "BCAC": "Business Credit Alliance Chain", + "BCAI": "Bright Crypto Ai", "BCAP": "Blockchain Capital", + "BCAPV1": "Blockchain Capital v1", "BCAT": "BitClave", "BCAU": "BetaCarbon", "BCB": "BCB Blockchain", @@ -1183,7 +1374,7 @@ "BD20": "BRC-20 DEX", "BDAY": "Birthday Cake", "BDB": "Big Data Block", - "BDC": "Based", + "BDC": "BILLION•DOLLAR•CAT", "BDCC": "BDCC COIN", "BDCLBSC": "BorderCollieBSC", "BDG": "BitDegree", @@ -1205,7 +1396,7 @@ "BEAMMW": "Beam", "BEAN": "Bean", "BEANS": "Moonbeans", - "BEAST": "CryptoBeast", + "BEAST": "MrBeast", "BEAT": "BEAT Token", "BEATLES": "JohnLennonC0IN", "BEATS": "Sol Beats", @@ -1214,8 +1405,11 @@ "BECH": "Beauty Chain", "BECN": "Beacon", "BECO": "BecoSwap Token", + "BECX": "BETHEL", "BED": "Bankless BED Index", "BEE": "Herbee", + "BEEF": "PepeBull", + "BEEG": "Beeg Blue Whale", "BEEP": "BEEP", "BEER": "BEERCOIN", "BEERUSCAT": "BeerusCat", @@ -1229,19 +1423,27 @@ "BEFX": "Belifex", "BEFY": "Befy Protocol", "BEG": "BEG", + "BEIBEI": "Chinese BEIBEI", "BEL": "Bella Protocol", "BELA": "Bela", + "BELL": "Bellscoin", + "BELLE": "Isabelle", + "BELLS": "Bellscoin", "BELR": "Belrium", "BELT": "Belt", "BELUGA": "Beluga", "BEM": "BEMIL Coin", + "BEMC": "BemChain", "BEMD": "Betterment Digital", "BEN": "Ben", "BEND": "BendDao", + "BENDER": "BENDER", "BENDOG": "Ben the Dog", "BENG": "Based Peng", + "BENI": "Beni", "BENJACOIN": "Benjacoin", - "BENJI": "BenjiRolls", + "BENJI": "Basenji", + "BENJIROLLS": "BenjiRolls", "BENK": "BENK", "BENT": "Bent Finance", "BENTO": "Bento", @@ -1252,9 +1454,12 @@ "BEPE": "Blast Pepe", "BEPR": "Blockchain Euro Project", "BEPRO": "BEPRO Network", + "BERF": "BERF", "BERN": "BERNcash", "BERNIE": "BERNIE SENDERS", "BERRY": "Berry", + "BERRYS": "BerrySwap", + "BERT": "Bertram The Pomeranian", "BES": "battle esports coin", "BESA": "Besa Gaming", "BESHARE": "Beshare Token", @@ -1264,6 +1469,7 @@ "BETACOIN": "BetaCoin", "BETBOX": "betbox", "BETF": "Betform", + "BETFI": "Betfin", "BETH": "Beacon ETH", "BETHER": "Bethereum", "BETR": "BetterBetting", @@ -1290,6 +1496,7 @@ "BFK WARZONE": "BFK Warzone", "BFLOKI": "BurnFloki", "BFLY": "Butterfly Protocol", + "BFM": "BenefitMine", "BFT": "BF Token", "BFTB": "Brazil Fan Token", "BFTC": "BITS FACTOR", @@ -1297,12 +1504,14 @@ "BG": "BunnyPark Game", "BGB": "Bitget token", "BGBP": "Binance GBP Stable Coin", + "BGBV1": "Bitget Token v1", "BGC": "Bee Token", "BGG": "BGG Token", "BGLD": "Based Gold", "BGONE": "BigONE Token", "BGPT": "BlockGPT", "BGS": "Battle of Guardians Share", + "BGSOL": "Bitget SOL Staking", "BGUY": "The Big Guy", "BGVT": "Bit Game Verse Token", "BHAO": "Bithao", @@ -1323,6 +1532,7 @@ "BIBL": "Biblecoin", "BIBO": "Bible of Memes", "BIC": "Bikercoins", + "BICHO": "bicho", "BICITY": "BiCity AI Projects", "BICO": "Biconomy", "BICS": "Biceps", @@ -1335,10 +1545,13 @@ "BIDP": "BID Protocol", "BIDR": "Binance IDR Stable Coin", "BIDZ": "BIDZ Coin", + "BIDZV1": "BIDZ Coin v1", "BIFI": "Beefy.Finance", "BIFIF": "BiFi", "BIG": "Big Eyes", "BIGBANGCORE": "BigBang Core", + "BIGCOIN": "BigCoin", + "BIGFOOT": "BigFoot Town", "BIGHAN": "BighanCoin", "BIGLEZ": "THE BIG LEZ SHOW", "BIGMIKE": "Big Mike", @@ -1350,11 +1563,17 @@ "BIIS": "biis (Ordinals)", "BIKI": "BIKI", "BILL": "TillBilly", + "BILLI": "Billi", "BILLICAT": "BilliCat", + "BILLY": "Billy ", + "BILLYBSC": "BILLY", "BIM": "BitminerCoin", + "BINANCED": "BinanceDog On Sol", + "BINANCEDOG": "Binancedog", "BIND": "Compendia", "BINEM": "Binemon", "BINGO": "Tomorrowland", + "BINK": "Big Dog Fink", "BINO": "Binopoly", "BINS": "Bitsense", "BINTEX": "Bintex Futures", @@ -1375,6 +1594,7 @@ "BIRD": "Bird.Money", "BIRDCHAIN": "Birdchain", "BIRDDOG": "Bird Dog", + "BIRDO": "Bird Dog", "BIS": "Bismuth", "BISKIT": "Biskit Protocol", "BISO": "BISOSwap", @@ -1395,11 +1615,13 @@ "BITCI": "Bitcicoin", "BITCM": "Bitcomo", "BITCNY": "bitCNY", + "BITCO": "Bitcoin Black Credit Card", "BITCOINC": "Bitcoin Classic", "BITCOINP": "Bitcoin Private", "BITCOINV": "BitcoinV", "BITCONNECT": "BitConnect Coin", "BITCRATIC": "Bitcratic Token", + "BITE": "Bitether", "BITF": "Bit Financial", "BITFLIP": "BitFlip", "BITG": "Bitcoin Green", @@ -1413,19 +1635,24 @@ "BITOK": "BitOKX", "BITORB": "BitOrbit", "BITRA": "Bitratoken", + "BITRADIO": "Bitradio", "BITREWARDS": "BitRewards", "BITROLIUM": "Bitrolium", "BITRUE": "Bitrue Coin", "BITS": "BitstarCoin", "BITSD": "Bits Digit", + "BITSERIAL": "BitSerial", "BITSILVER": "bitSilver", "BITSPACE": "Bitspace", "BITSZ": "Bitsz", "BITT": "BiTToken", "BITTO": "BITTO", + "BITTON": "Bitton", "BITUNE": "Bitune", "BITUSD": "bitUSD", + "BITV": "Bitvolt", "BITVOLT": "BitVolt", + "BITWORLD": "Bit World Token", "BITX": "BitScreener", "BITZ": "Bitz Coin", "BIUT": "Bit Trust System", @@ -1442,19 +1669,23 @@ "BKING": "King Arthur", "BKK": "BKEX Token", "BKN": "Brickken", + "BKOK": "BKOK FinTech", "BKPT": "Biokript", "BKR": "Balkari Token", "BKRW": "Binance KRW", "BKS": "Barkis Network", "BKT": "Blocktrade token", "BKX": "BANKEX", + "BL00P": "BLOOP", "BLA": "BlaBlaGame", "BLAC": "Blacksmith Token", "BLACK": "BLACKHOLE PROTOCOL", "BLACKD": "Blackder AI", "BLACKDRAGON": "Black Dragon", + "BLACKR": "BLACK ROCK", "BLACKROCK": "BlackRock", "BLACKSALE": "Black Sale", + "BLACKST": "Black Stallion", "BLACKSWAN": "BlackSwan AI", "BLADE": "BladeWarrior", "BLAKEBTC": "BlakeBitcoin", @@ -1462,6 +1693,7 @@ "BLAS": "BlakeStar", "BLAST": "BLAST", "BLASTA": "BlastAI", + "BLASTUP": "BlastUP", "BLAUNCH": "B-LAUNCH", "BLAZE": "Blaze", "BLAZEX": "BlazeX", @@ -1474,11 +1706,13 @@ "BLEPE": "Blepe", "BLERF": "BLERF", "BLES": "Blind Boxes", + "BLET": "Brainlet", "BLF": "Baby Luffy", "BLHC": "BlackholeCoin", "BLI": "BALI TOKEN", "BLID": "Bolide", "BLIN": "Blin Metaverse", + "BLIND": "Blindsight", "BLING": "PLEB DREKE", "BLINK": "BlockMason Link", "BLINU": "Baby Lambo Inu", @@ -1488,11 +1722,14 @@ "BLKC": "BlackHat Coin", "BLKD": "Blinked", "BLKS": "Blockshipping", + "BLM": "Blombard", "BLN": "Bulleon", "BLNM": "Bolenum", "BLOB": "Blob", "BLOC": "Blockcloud", "BLOCK": "Blockasset", + "BLOCKB": "Block Browser", + "BLOCKF": "Block Farm Club", "BLOCKIFY": "Blockify.Games", "BLOCKN": "BlockNet", "BLOCKPAY": "BlockPay", @@ -1502,6 +1739,7 @@ "BLOCKW": "Blockwise", "BLOCM": "BLOC.MONEY", "BLOCX": "BLOCX.", + "BLOGGE": "Bloggercube", "BLOK": "Bloktopia", "BLOO": "bloo foster coin", "BLOODY": "Bloody Token", @@ -1509,6 +1747,7 @@ "BLOOMT": "Bloom Token", "BLOVELY": "Baby Lovely Inu", "BLOX": "BLOX", + "BLOXT": "Blox Token", "BLP": "BullPerks", "BLRY": "BillaryCoin", "BLS": "Blocks Space", @@ -1518,14 +1757,22 @@ "BLTG": "Block-Logic", "BLTV": "BLTV Token", "BLU": "BlueCoin", - "BLUE": "Ethereum Blue", + "BLUB": "BLUB", + "BLUE": "Blue Protocol", + "BLUEBUTT": "BLUE BUTT CHEESE", + "BLUEG": "Blue Guy", "BLUEM": "BlueMove", + "BLUEN": "Blue Norva", "BLUES": "Blueshift", + "BLUESC": "BluesCrypto", "BLUESPARROW": "BlueSparrow Token", "BLUESPARROWOLD": "BlueSparrowToken", + "BLUEW": "Blue Whale", + "BLUFF": "BluffCat", "BLUI": "Blui", "BLUR": "Blur", "BLURT": "Blurt", + "BLUSD": "Boosted LUSD", "BLUT": "Bluetherium", "BLV": "Blockvest", "BLV3": "Crypto Legions V3", @@ -1554,8 +1801,11 @@ "BMON": "Binamon", "BMONEY": "B-money", "BMP": "Brother Music Platform", + "BMS": "BMS COIN", "BMT": "BMChain", + "BMTC": "Metabit", "BMW": "BMW", + "BMWUKONG": "Black Myth WuKong", "BMX": "BitMart Token", "BMXT": "Bitmxittz", "BMXX": "Multiplier", @@ -1566,9 +1816,12 @@ "BNBBUNNY": "BNB BUNNY", "BNBCAT": "BNBcat", "BNBCH": "BNB Cash", + "BNBD": "BNBDOG", "BNBDOG": "BNB DOG INU", "BNBDOGE": "BNBdoge", "BNBDRGN": "BNBDragon", + "BNBE": "BNBEE", + "BNBFLOKI": "BNB FLOKI", "BNBFROG": "BNBFROG", "BNBH": "BnbHeroes Token", "BNBLION": "BNB LION", @@ -1586,6 +1839,7 @@ "BNIX": "BNIX Token", "BNK": "Bankera", "BNN": "Banyan Network", + "BNOM": "BitNomad", "BNP": "BenePit", "BNPL": "BNPL Pay", "BNR": "BiNeuro", @@ -1593,7 +1847,9 @@ "BNS": "BNS token", "BNSAI": "bonsAI Network", "BNSD": "BNSD Finance", + "BNSOL": "Binance Staked SOL", "BNSOLD": "BNS token ", + "BNSV1": "BNS token v1", "BNSX": "Bitcoin Name Service System", "BNT": "Bancor Network Token", "BNTE": "Bountie", @@ -1602,8 +1858,10 @@ "BNU": "ByteNext", "BNUSD": "Balanced Dollars", "BNX": "BinaryX", + "BNY": "TaskBunny", "BOA": "BOSAGORA", "BOAI": "BOLICAI", + "BOAM": "BOOK OF AI MEOW", "BOARD": "SurfBoard Finance", "BOAT": "Doubloon", "BOBA": "Boba Network", @@ -1613,7 +1871,10 @@ "BOBBYM": "Bobby Moore", "BOBC": "Bobcoin", "BOBE": "BOOK OF BILLIONAIRES", + "BOBER": "BOBER", + "BOBFUN": "BOB", "BOBO": "BOBO", + "BOBOT": "Bobo The Bear", "BOBS": "Bob's Repair", "BOBT": "BOB Token", "BOBUKI": "Bobuki Neko", @@ -1621,6 +1882,7 @@ "BOCA": "BookOfPussyCats", "BOCAC": "BocaChica token", "BOCAT": "BOCAT", + "BOD": "Book of Donald Trump", "BODA": "Based Yoda", "BODAV2": "BODA Token", "BODE": "Book of Derp", @@ -1630,13 +1892,16 @@ "BODOG": "Book of Doge", "BODYP": "Body Profile", "BOE": "Bodhi", + "BOF": "Balls of Fate", "BOG": "Bogged Finance", "BOGCOIN": "Bogcoin", + "BOGD": "Bogdanoff", "BOGE": "Boge", "BOGEY": "Bogey", "BOGGY": "Boggy Coin", "BOJAK": "Based Wojak", "BOJI": "BOJI Token", + "BOJIV1": "BOJI Token v1", "BOK": "Blockium", "BOKI": "BOOK OF KILLER", "BOKU": "Boryoku Dragonz", @@ -1645,16 +1910,19 @@ "BOLI": "BolivarCoin", "BOLT": "Bolt", "BOLTT": "BolttCoin", + "BOM": "Book Of Matt Furie", "BOMA": "Book of Maga", "BOMB": "BOMB", "BOMBC": "BombCoin", "BOMBM": "Bomb Money", + "BOMBO": "BOMBO", "BOMBS": "Bomb Shelter Inu", "BOME": "BOOK OF MEME", "BOME2": "Book of Meme 2.0", "BOMEDOGE": "BOOK OF DOGE MEMES", "BOMEOW": "Book of Meow", "BOMES": "BOOK OF MEMES", + "BOMET": "BOME TRUMP", "BOMK": "BOMK", "BON": "Bonpay", "BONA": "Bonafi", @@ -1663,19 +1931,27 @@ "BONDLY": "Bondly", "BONDLYV1": "Bondly Finance", "BONE": "Bone ShibaSwap", - "BONES": "BonesCoin", + "BONES": "Moonshots Farm", + "BONESCOIN": "BonesCoin", + "BONESV1": "Squirrel Finance", "BONFIRE": "Bonfire", "BONG": "BonkWifGlass", + "BONGO": "BONGO CAT", "BONIX": "Blockonix", "BONK": "Bonk", + "BONKBNB": "Bonk BNB", "BONKCON": "Bonkcon", + "BONKEA": "Bonk Earn", + "BONKEY": "Bonkey", "BONKFA": "Bonk of America", "BONKFORK": "BonkFork", "BONKGROK": "Bonk Grok", "BONKH": "BonkHoneyHNTMobileSOL", "BONKIN": "Bonkinu", "BONKKONG": "BONK KONG", + "BONKONBASE": "Bonk on Base", "BONKONETH": "Bonk On ETH", + "BONKW": "bonkwifhat", "BONO": "Bonorum Coin", "BONTE": "Bontecoin", "BONUS": "BonusBlock", @@ -1686,15 +1962,23 @@ "BOOFI": "Boo Finance", "BOOK": "Solbook", "BOOKIE": "BookieBot", - "BOOM": "BOOM DAO", + "BOOKO": "Book of Pets", + "BOOKOF": "BOOK OF NOTHING", + "BOOM": "Boomco", "BOOMCOIN": "Boom Token", + "BOOMDAO": "BOOM DAO", "BOOMER": "Boomer", "BOONS": "BOONSCoin", "BOOP": "Boop", - "BOOST": "Boost", + "BOOS": "Boost Trump Campaign", + "BOOST": "PodFast", + "BOOSTCO": "Boost", "BOOSTO": "BOOSTO", "BOOT": "Bostrom", "BOP": "Boring Protocol", + "BOPB": "BIOPOP", + "BOPE": "Book of Pepe", + "BOPPY": "BOPPY", "BOR": "BoringDAO", "BORA": "BORA", "BORED": "Bored Museum", @@ -1702,6 +1986,7 @@ "BORING": "BoringDAO", "BORK": "Bork", "BORKIE": "Borkie", + "BORPA": "Borpa", "BORUTO": "Boruto Inu", "BOS": "BOScoin", "BOSE": "Bitbose", @@ -1724,32 +2009,48 @@ "BOUTS": "BoutsPro", "BOW": "Archer Swap", "BOWE": "Book of Whales", + "BOWSC": "BowsCoin", + "BOWSER": "Bowser", "BOX": "ContentBox", "BOXETH": "Cat-in-a-Box Ether", "BOXT": "BOX Token", "BOXX": "Blockparty", "BOXY": "BoxyCoin", - "BOYS": "CRASHBOYS", + "BOYS": "BOYSCLUB (boysclubonbase.com)", + "BOYSC": "Boy's club", "BOYSCLUB": "Matt Furie's Boys Club", "BOZO": "BOZO", "BOZOH": "bozo Hybrid", "BOZY": "Book of Crazy", "BP": "BunnyPark", "BPAD": "BlokPad", + "BPADA": "Binance-Peg Cardano (Binance Bridge)", + "BPAVAX": "Binance-Peg Avalanche (Binance Bridge)", "BPAY": "BNBPay", + "BPBCH": "Binance-Peg Bitcoin Cash (Binance Bridge)", + "BPBTT": "Binance-Peg BitTorrent", "BPD": "Beautiful Princess Disorder", + "BPDAI": "Binance-Peg Dai (Binance Bridge)", + "BPDOGE": "Binance-Peg DogeZilla (Binance Bridge)", "BPEPEF": "Baby Pepe Floki", "BPET": "BPET", "BPINKY": "BPINKY", "BPL": "BlockPool", "BPLC": "BlackPearl Token", + "BPLINK": "Binance-Peg Chainlink (Binance Bridge)", + "BPLTC": "Binance-Peg Litecoin", + "BPMATIC": "Binance-Peg Polygon (Binance Bridge)", "BPN": "beepnow", + "BPNEAR": "Binance-Peg NEAR Protocol", "BPOKO": "BabyPoko", "BPRIVA": "Privapp Network", "BPRO": "BitCloud Pro", "BPS": "BitcoinPoS", + "BPSHIB": "Binance-Peg Shiba Inu (Binance Bridge)", "BPT": "BlackPool Token", "BPTC": "Business Platform Tomato Coin", + "BPUNI": "Binance-Peg Uniswap Protocol Token (Binance Bridge)", + "BPUSDC": "Binance-Peg USD Coin (Binance Bridge)", "BPX": "Black Phoenix", "BQ": "Bitqy", "BQC": "BQCoin", @@ -1761,11 +2062,12 @@ "BRACE": "Bitci Racing Token", "BRAIN": "BrainCoin", "BRAINERS": "Brainers", + "BRAINLET": "Brainlet", "BRAINZ": "Brainz Finance", "BRAM": "Defibox bRAM", "BRANA": "Branaverse", "BRAND": "BrandProtect", - "BRAT": "BROTHER", + "BRAT": "Peak Brat", "BRATT": "Son of Brett", "BRAWL": "BitBrawl", "BRAZ": "Brazio", @@ -1780,10 +2082,13 @@ "BREE": "CBDAO", "BREED": "BreederDAO", "BREPE": "BREPE", - "BRETT": "Brett", + "BRETARDIO": "Bretardio", + "BRETT": "Brett Base", "BRETTA": "Bretta", - "BRETTBASE": "Brett Base", - "BRETTETH": "Brett ETH", + "BRETTFYI": "Brett", + "BRETTGOLD": "Brett Gold", + "BRETTONETH": "Brett ETH", + "BRETTSUI": "Brett (brettsui.com)", "BREW": "CafeSwap Token", "BREWERY": "Brewery Consortium Coin", "BREWLABS": "Brewlabs", @@ -1792,7 +2097,8 @@ "BRGX": "Bridge$", "BRI": "Baroin", "BRIA": "Briacoin", - "BRIAN": "Brianwifhat", + "BRIAN": "Brian Arm Strong", + "BRIANWIF": "Brianwifhat", "BRIC": "BrightCoin", "BRICK": "Brickchain FInance", "BRICKS": "MyBricks", @@ -1800,6 +2106,7 @@ "BRIGHT": "Bright Token", "BRIGHTU": "Bright Union", "BRIK": "BrikBit", + "BRIL": "Brilliantcrypto", "BRISE": "Bitgert", "BRIT": "BritCoin", "BRITT": "Britt", @@ -1813,17 +2120,22 @@ "BRN": "BRN Metaverse", "BRNK": "Brank", "BRNX": "Bronix", - "BRO": "Bitradio", + "BRO": "Bro the cat", "BROCK": "Bitrock", "BROGG": "Brett's Dog", + "BROKE": "Broke Again", + "BROKIE": "Brokie", "BRONZ": "BitBronze", "BROOT": "BROOT", + "BROTHER": "BROTHER", "BROWN": "BrowniesSwap", + "BROZ": "Brozinkerbell", "BRRR": "Burrow", "BRS": "Broovs Projects", "BRT": "Bikerush", "BRTR": "Barter", "BRTX": "Bertinity", + "BRUH": "Bruh", "BRUNE": "BitRunes", "BRUSH": "PaintSwap", "BRUV": "Bruv", @@ -1839,17 +2151,20 @@ "BSAFU": "BlockSAFU", "BSATOSHI": "BabySatoshi", "BSB": "Based Street Bets", - "BSC": "BowsCoin", + "BSC": "BSC Layer", "BSCAKE": "Bunscake", "BSCBURN": "BSCBURN", + "BSCC": "BSCCAT", "BSCGIRL": "Binance Smart Chain Girl", "BSCH": "BitSchool", "BSCM": "BSC MemePad", "BSCPAD": "BSCPAD", "BSCPAY": "BSC PAYMENTS", "BSCS": "BSC Station", + "BSCST": "Starter", "BSCV": "Bscview", - "BSE": "BitSerial", + "BSDETH": "Based ETH", + "BSE": "base season", "BSEND": "BitSend", "BSFM": "BABY SAFEMOON", "BSG": "Baby Squid Game", @@ -1858,6 +2173,7 @@ "BSHARE": "Bomb Money", "BSHIB": "Based Shiba Inu", "BSI": "Bali Social Integrated", + "BSK": "BTCSKR", "BSKT": "BasketCoin", "BSL": "BankSocial", "BSOL": "BlazeStake Staked SOL", @@ -1894,12 +2210,15 @@ "BTC": "Bitcoin", "BTC2": "Bitcoin 2", "BTC2XFLI": "BTC 2x Flexible Leverage Index", + "BTC70000": "BTC 70000", "BTCA": "BITCOIN ADDITIONAL", "BTCAB": "Bitcoin Avalanche Bridged", + "BTCACT": "BITCOIN Act", "BTCAS": "BitcoinAsia", "BTCAT": "Bitcoin Cat", "BTCB": "Bitcoin BEP2", "BTCBR": "Bitcoin BR", + "BTCBRV1": "Bitcoin BR v1", "BTCC": "Bitcoin Core", "BTCD": "BitcoinDark", "BTCDRAGON": "BTC Dragon", @@ -1916,6 +2235,7 @@ "BTCM": "BTCMoon", "BTCMT": "Minto", "BTCN": "BitcoiNote", + "BTCNOW": "Blockchain Technology Co.", "BTCP": "Bitcoin Palladium", "BTCPAY": "Bitcoin Pay", "BTCPX": "BTC Proxy", @@ -1931,7 +2251,9 @@ "BTCZ": "BitcoinZ", "BTD": "Bitcloud", "BTDX": "Bitcloud 2.0", - "BTE": "BTEcoin", + "BTE": "Betero", + "BTECOIN": "BTEcoin", + "BTEV1": "Betero v1", "BTEX": "BTEX", "BTF": "Blockchain Traded Fund", "BTFA": "Banana Task Force Ape", @@ -1944,6 +2266,7 @@ "BTMG": "Bitcademy Football", "BTMI": "BitMiles", "BTMK": "BitMark", + "BTMT": "BITmarkets Token", "BTMXBULL": "3X Long BitMax Token Token", "BTNT": "BitNautic Token", "BTNTV2": "BitNautic Token", @@ -1993,11 +2316,14 @@ "BUCKY": "Bucky", "BUD": "Buddy", "BUDDHA": "Buddha", + "BUDDY": "BUDDY", "BUDG": "Bulldogswap", + "BUENO": "Bueno", "BUF": "Buftoad", "BUFF": "Buffalo Swap", "BUFFDOGE": "Buff Doge", "BUFFET": "Worried", + "BUFFI": "Bufficorn", "BUGATTI": "BUGATTI", "BUGG": "Bugg Inu", "BUGS": "Bugs Bunny", @@ -2007,15 +2333,20 @@ "BUILDTEAM": "BuildTeam", "BUK": "CryptoBuk", "BUL": "bul", + "BULDAK": "Buldak", "BULEI": "Bulei", "BULL": "Bullieverse", "BULLC": "BuySell", + "BULLF": "BULL FINANCE", + "BULLI": "Bullish On Ethereum", "BULLINU": "Bull inu", "BULLION": "BullionFX", + "BULLISH": "bullish", "BULLMOON": "Bull Moon", "BULLPEPE": "Bullpepe", "BULLS": "Bull Coin", "BULLSH": "Bullshit Inu", + "BULLY": "Dolos The Bully", "BULLYINGCAT": "Bullying Cat", "BULT": "Bullit", "BUM": "WillyBumBum", @@ -2025,9 +2356,12 @@ "BUND": "Bund V2.0", "BUNDL": "Bundl Tools", "BUNI": "Bunicorn", + "BUNN": "Bunni", "BUNNY": "Pancake Bunny", "BUNNYINU": "Bunny Inu", + "BUNNYM": "BUNNY MEV BOT", "BUNNYROCKET": "BunnyRocket", + "BURG": "Burger", "BURGER": "Burger Swap", "BURN": "BurnedFi", "BURNDOGE": "BurnDoge", @@ -2042,10 +2376,12 @@ "BUSDC": "BUSD", "BUSY": "Busy DAO", "BUT": "BitUP Token", + "BUTT": "Buttercat", "BUX": "BUX", "BUXCOIN": "Buxcoin", "BUY": "Burency", "BUYI": "Buying.com", + "BUYT": "Buy the DIP", "BUZZ": "BuzzCoin", "BV3A": "Buccaneer V3 Arbitrum", "BVC": "BeaverCoin", @@ -2053,7 +2389,8 @@ "BVND": "Binance VND", "BVO": "BRAVO Pay", "BVT": "BovineVerse Token", - "BWB": "Bit World Token", + "BWB": "Bitget Wallet Token", + "BWEN": "Baby Wen", "BWF": "Beowulf", "BWJ": "Baby WOJ", "BWK": "Bulwark", @@ -2063,6 +2400,7 @@ "BWS": "BitcoinWSpectrum", "BWT": "Bittwatt", "BWT2": "Bitwin 2.0", + "BWULL": "Bwull", "BWX": "Blue Whale", "BX": "BlockXpress", "BXA": "Blockchain Exchange Alliance", @@ -2081,12 +2419,14 @@ "BYAT": "Byat", "BYC": "ByteCent", "BYG": "Black Eye Galaxy", + "BYT": "ByteAI", "BYTE": "Byte", "BYTES": "Neo Tokyo", "BYTHER": "Bytether ", "BYTS": "Bytus", "BYTZ": "BYTZ", "BZ": "Bit-Z", + "BZE": "BeeZee", "BZENIQ": "Wrapped Zeniq (BNB)", "BZET": "Bzetcoin", "BZKY": "Bizkey", @@ -2097,7 +2437,6 @@ "BZX": "Bitcoin Zero", "BZZ": "Swarmv", "BZZONE": "Bzzone", - "BamitCoin": "BAM", "C2": "Coin.2", "C20": "Crypto20", "C25": "C25 Coin", @@ -2115,6 +2454,7 @@ "CACHE": "Cache", "CACHEGOLD": "CACHE Gold", "CACTUS": "CACTUS", + "CADAI": "CADAI", "CADC": "CAD Coin", "CADINU": "Canadian Inuit Dog", "CADN": "Content and AD Network", @@ -2144,6 +2484,7 @@ "CAMEL": "The Camel", "CAMLY": "Camly Coin", "CAMP": "Camp", + "CAMT": "CAMELL", "CAN": "Channels", "CANCER": "Cancer", "CAND": "Canary Dollar", @@ -2176,6 +2517,7 @@ "CARDSWAP": "CardSwap", "CARE": "Carebit", "CARES": "CareCoin", + "CARL": "Carl", "CARLO": "Carlo", "CARO": "Meta Ricaro", "CAROL": "CAROLToken", @@ -2185,46 +2527,67 @@ "CART": "CryptoArt.Ai", "CARTAXI": "CarTaxi", "CARTERCOIN": "CarterCoin", + "CARV": "CARV", "CAS": "Cashaa", "CASH": "CashCoin", + "CASHLY": "Cashly", "CASHT": "Cash Tech", "CASINU": "Casinu Inu", "CASIO": "CasinoXMetaverse", "CASPER": "Casper DeFi", "CAST": "Castello Coin", "CASTLE": "bitCastle", - "CAT": "Cat Token", + "CAT": "Simon's Cat", "CATA": "CATAMOTO", + "CATABSC": "CATA BSC", "CATAI": "Catgirl AI", + "CATALORIAN": "CATALORIAN", + "CATANA": "Catana", "CATBA": "CATBA INU", + "CATBAL": "Catbal", "CATBOY": "Catboy", "CATC": "Catcoin", "CATCEO": "CATCEO", "CATCH": "SpaceCatch", - "CATCOIN": "CatCoin Cash", + "CATCO": "CatCoin", + "CATCOIN": "CatCoin", "CATCOINETH": "Catcoin", + "CATCOINV2": "CatCoin Cash", + "CATDOG": "Cat-Dog", "CATDOGE": "CAT DOGE", - "CATE": "CateCoin", + "CATEC": "Cate Coin", + "CATECOIN": "CateCoin", "CATELON": "CatElonMars", "CATEX": "CATEX", "CATFISH": "Catfish", "CATGAME": "Cookie Cat Game", "CATGIRL": "Catgirl", + "CATGOKU": "Catgoku", "CATGPT": "CatGPT", "CATHAT": "catwifhat", "CATHEON": "Catheon Gaming", "CATHERO": "Cat Hero", + "CATI": "Catizen", + "CATINU": "CAT INU", "CATKING": "CAT KING", + "CATLIFE": "Cat Life", "CATMAN": "Catman", "CATME": "ELON’S CAT", "CATO": "CATO", "CATPAY": "CATpay", "CATPEPE": "CAT PEPE", "CATS": "CatCoin Token", + "CATSC": "Catscoin", "CATSHIRA": "Shira Cat", + "CATSO": "Cats Of Sol", + "CATSON": "Catson", + "CATSV1": "CatCoin Token v1", + "CATSY": "CAT SYLVESTER", "CATT": "Catex", + "CATTO": "Cat Token", "CATVAX": "Catvax", "CATVILLS": "Catvills Coin", + "CATW": "Cat wif Hands", "CATWARRIOR": "Cat warrior", "CATWIF": "CatWifHat", "CATX": "CAT.trade Protocol", @@ -2241,8 +2604,11 @@ "CBABY": "Cosmo Baby", "CBANK": "Crypto Bank", "CBAT": "Compound Basic Attention Token", + "CBBTC": "Coinbase Wrapped BTC", + "CBBTCBASE": "cbBTC", "CBC": "Casino Betting Coin", "CBD": "CBD Crystals", + "CBDAI": "Dai (Cronos Bridge)", "CBDC": "CannaBCoin", "CBDG": "CBD Global", "CBE": "The Chain of Business Entertainment", @@ -2252,10 +2618,12 @@ "CBG": "Chainbing", "CBIXP": "Cubiex Power", "CBK": "Cobak Token", + "CBL": "Credbull", "CBM": "CryptoBonusMiles", "CBNT": "Create Breaking News Together", "CBOT": "C-BOT", - "CBP": "ComBox", + "CBP": "CashBackPro", + "CBPAY": "COINBAR PAY", "CBRL": "Crypto BRL", "CBRT": "Cybereits Token", "CBS": "Cerberus", @@ -2277,6 +2645,7 @@ "CCC": "CCCoin", "CCCX": "Clipper Coin Capital", "CCD": "Concordium", + "CCDS": "CCDS INTERNATIONAL", "CCE": "CloudCoin", "CCGDS": "CCGDS", "CCH": "Coinchase", @@ -2311,6 +2680,7 @@ "CDX": "CDX Network", "CDY": "Bitcoin Candy", "CDragon": "Clumsy Dragon", + "CEC": "Counterfire Economic Coin", "CEDEX": "CEDEX Coin", "CEEK": "CEEK Smart VR Token", "CEFS": "CryptopiaFeeShares", @@ -2326,12 +2696,17 @@ "CEM": "Crypto Emergency", "CEN": "Coinsuper Ecosystem Network", "CENNZ": "Centrality Token", + "CENS": "Censored Ai", "CENT": "CENTERCOIN", + "CENTA": "Centaurify", "CENTRA": "Centra", + "CENTS": "Centience", "CENX": "Centcex", "CEODOGE": "CEO DOGE", + "CERBER": "CERBEROGE", "CERE": "Cere Network", "CERES": "Ceres", + "CES": "swap.coffee", "CESC": "Crypto Escudo", "CET": "CoinEx Token", "CETH": "Compound Ethereum", @@ -2348,6 +2723,7 @@ "CFL365": "CFL365 Finance", "CFLASH": "Flash", "CFLO": "Chain Flowers", + "CFN": "Cockfight Network", "CFT": "CryptoForecast", "CFTY": "Crafty", "CFX": "Conflux Network", @@ -2355,11 +2731,13 @@ "CFXT": "Chainflix", "CFun": "CFun", "CGA": "Cryptographic Anomaly", + "CGAR": "CryptoGuards", "CGG": "Chain Guardians", "CGL": "Crypto Gladiator Shards", "CGLD": "Celo Gold", "CGO": "Comtech Gold", "CGPT": "ChainGPT", + "CGPU": "CloudGPU", "CGS": "Crypto Gladiator Shards", "CGT": "Coin Gabbar Token", "CGU": "Crypto Gaming United", @@ -2369,6 +2747,7 @@ "CHAD": "Chad Coin", "CHADCAT": "CHAD CAT", "CHADS": "CHADS VC", + "CHAI": "Chroma AI", "CHAIN": "Chain Games", "CHAINCADE": "ChainCade", "CHAL": "Chalice Finance", @@ -2377,8 +2756,10 @@ "CHAMPZ": "Champz", "CHAN": "ChanCoin", "CHANCE": "Ante Casino", + "CHANG": "Chang", "CHANGE": "ChangeX", "CHAO": "23 Skidoo", + "CHAOS": "chaos and disorder", "CHAPZ": "Chappyz", "CHARGED": "GoCharge Tech", "CHARIZARD": "Charizard Inu", @@ -2389,7 +2770,9 @@ "CHARTIQ": "ChartIQ", "CHASH": "CleverHash", "CHAT": "Solchat", + "CHATAI": "ChatAI Token", "CHATGPT": "AI Dragon", + "CHATTY": "ChatGPT's Mascot", "CHB": "COINHUB TOKEN", "CHBR": "CryptoHub", "CHC": "ChainCoin", @@ -2404,8 +2787,11 @@ "CHEEMS": "Cheems", "CHEEPEPE": "CHEEPEPE", "CHEERS": "DICAPRIO CHEERS", - "CHEESE": "CHEESE", + "CHEESE": "Cheese", + "CHEESEBALL": "Cheeseball the Wizard", + "CHEESECOIN": "Cheesecoin", "CHEESUS": "Cheesus", + "CHEF": "Chefdotfun", "CHENG": "Chengshi", "CHEQ": "CHEQD Network", "CHER": "Cherry Network", @@ -2413,35 +2799,47 @@ "CHESS": "Tranchess", "CHESSCOIN": "ChessCoin", "CHET": "ChetGPT", + "CHEW": "CHEWY", "CHEWY": "Chewy", "CHEX": "Chintai", + "CHEYENNE": "Cheyenne", "CHFN": "NOKU CHF", "CHFT": "Crypto Holding", "CHFU": "Upper Swiss Franc", "CHFX": "eToro Swiss Franc", "CHH": "Chihuahua Token", "CHI": "Chi Gastoken", + "CHIB": "Chiba Inu", "CHICA": "CHICA", "CHICKS": "SolChicks", "CHIDO": "Chinese Doge Wow", + "CHIE": "Chief Pepe Officer", "CHIEF": "TheChiefCoin", + "CHIEFD": "Chief D.O.G.E", "CHIHUA": "Chihua Token", "CHII": "Chiiper Chain", "CHILD": "ChildCoin", + "CHILDAI": "Singularity's Child gonzoai", "CHILI": "CHILI", "CHILL": "ChillPill", + "CHILLGUY": "Chill Guy", "CHIM": "Chimera", + "CHINAU": "Chinau", "CHINAZILLA": "ChinaZilla", "CHINGON": "Mexico Chingon", "CHINU": "Chubby Inu", "CHIP": "Chip", + "CHIPI": "chipi", "CHIPPY": "Chippy", "CHIPS": "CHIPS", "CHIRP": "Chirp", + "CHIRPY": "Chirpy Boy", + "CHITAN": "Chitan", "CHITCAT": "ChitCAT", "CHIWAWA": "Chiwawa", "CHK": "Chek", "CHKN": "Chickencoin", + "CHLOE": "Pnut's Sister", "CHLT": "Chellitcoin", "CHMB": "Chumbi Valley", "CHMPZ": "Chimpzee", @@ -2473,7 +2871,10 @@ "CHUC": "CHUCK", "CHUCHU": "CHUCHU", "CHUCK": "Chuck Norris", - "CHUMP": "Chump Change", + "CHUD": "Chudjak", + "CHULO": "Papichulo", + "CHUMP": "Donald J Chump", + "CHUMPC": "Chump Change", "CHURRO": "CHURRO-The Jupiter Dog", "CHVF": "Chives Finance", "CHW": "Chrysalis Coin", @@ -2484,6 +2885,7 @@ "CIC": "Crazy Internet Coin", "CICHAIN": "CIChain", "CIF": "Crypto Improvement Fund", + "CIG": "cig", "CIM": "COINCOME", "CIN": "CinderCoin", "CIND": "Cindrum", @@ -2499,6 +2901,7 @@ "CIRCUS": "Cirque Du Sol", "CIRRUS": "Cirrus", "CIRUS": "Cirus", + "CIRX": "Circular Protocol", "CITI": "CITI Fediverse", "CITY": "Manchester City Fan Token", "CIV": "Civilization", @@ -2520,7 +2923,10 @@ "CL": "CoinLancer", "CLA": "ClaimSwap", "CLAM": "CLAMS", + "CLANKER": "tokenbot", + "CLAP": "Clap Cat", "CLAS": "Classic USDC", + "CLASH": "Clashub", "CLASS": "Class Coin", "CLAY": "Clay Nation", "CLB": "Cloudbric", @@ -2544,6 +2950,7 @@ "CLINT": "Clinton", "CLIPS": "Clips", "CLIQ": "DefiCliq", + "CLISBNB": "clisBNB", "CLIST": "Chainlist", "CLM": "CoinClaim", "CLMRS": "Crolon Mars", @@ -2551,11 +2958,14 @@ "CLNX": "Coloniume Network", "CLNY": "Colony", "CLO": "Callisto Network", + "CLOA": "Cloak", "CLOAK": "CloakCoin", + "CLOKI": "CATLOKI", "CLORE": "Clore.ai", - "CLOUD": "Metacloud", + "CLOUD": "Cloud", "CLOUT": "BitClout", "CLOUTIO": "Clout", + "CLOW": "Clown Pepe", "CLPX": "Chynge.net", "CLR": "CopperLark", "CLRTY": "Clarity", @@ -2576,6 +2986,7 @@ "CMCX": "CORE MultiChain", "CMDX": "Comdex", "CMERGE": "CoinMerge", + "CMETH": "Mantle Restaked Ether", "CMFI": "Compendium", "CMINER": "ChainMiner", "CMIT": "CMITCOIN", @@ -2627,13 +3038,20 @@ "CNYX": "eToro Chinese Yuan", "CO": "Corite", "CO2": "CO2 Token", + "COAI": "CodeMong Ai", "COAL": "BitCoal", "COB": "Cobinhood", "COBE": "Castle of Blackwater", + "COBY": "Coby", "COC": "Coin of the champions", + "COCAINE": "THE GOOD STUFF", "COCK": "Shibacock", - "COCO": "0xCoco", + "COCO": "COCO COIN", + "COCONUT": "Coconut", + "COD": "Chief of Deswamp", + "CODA": "CODA", "CODAI": "CODAI", + "CODE": "Code Token", "CODEG": "CodeGenie", "CODEO": "Codeo Token", "CODEX": "CODEX Finance", @@ -2641,6 +3059,7 @@ "CODY": "Coindy", "COE": "CoEval", "COFEEE": "COFEEE", + "COFFEE": "COFFEE", "COFFEECOIN": "CoffeeCoin", "COFI": "CoinFi", "COFIX": "CoFIX", @@ -2650,15 +3069,19 @@ "COGI": "COGI", "COGS": "Cogmento", "COI": "Coinnec", + "COINB": "Coinbidex", "COINBT": "CoinBot", "COINDEFI": "Coin", "COING": "Coingrid", "COINH": "Coinhound", "COINLION": "CoinLion", + "COINM": "CoinMarketPrime", + "COINONAT": "Coinonat", "COINSCOPE": "Coinscope", "COINSL": "CoinsLoot", "COINVEST": "Coinvest", "COINYE": "Coinye West", + "COJ": "Cojam", "COK": "Cat Own Kimono", "COKE": "Cocaine Cowboy Shards", "COL": "Clash of Lilliput", @@ -2666,8 +3089,10 @@ "COLL": "Collateral Pay", "COLLAR": "PolyPup Finance", "COLLE": "Collective Care", + "COLLEA": "Colle AI", "COLLECT": "CoinCollect", "COLLG": "Collateral Pay Governance", + "COLON": "Colon", "COLR": "colR Coin", "COLT": "Collateral Network", "COLX": "ColossusCoinXT", @@ -2675,6 +3100,8 @@ "COMAI": "Commune AI", "COMB": "Combo", "COMBO": "COMBO", + "COMBOX": "ComBox", + "COMC": "ComCrica Token", "COME": "Community of Meme", "COMEW": "Coin In Meme World", "COMFI": "CompliFi", @@ -2696,15 +3123,20 @@ "CONK": "ShibaPoconk", "CONS": "ConSpiracy Coin", "CONSENTIUM": "Consentium", + "CONTROL": "Control Token", "CONV": "Convergence", "CONX": "Connex", + "COO": "Cool Cats MILK", "COOCHIE": "Cucci", "COOHA": "CoolHash", "COOK": "Cook", + "COOKIE": "Cookie", "COOL": "CoolCoin", "COOP": "Coop Network", + "COPA": "COCO PARK", "COPE": "Cope", "COPI": "Cornucopias", + "COPIO": "Copiosa Coin", "COPIUM": "Copium", "COPS": "Cops Finance", "COPYCAT": "Copycat Finance", @@ -2712,8 +3144,10 @@ "COR": "Coreto", "CORAL": "CoralPay", "CORE": "Core", + "COREC": "CoreConnect", "COREDAO": "coreDAO", "COREG": "Core Group Asset", + "COREK": "Core Keeper", "COREUM": "Coreum", "CORGI": "Corgi Inu", "CORGIAI": "CorgiAI", @@ -2721,12 +3155,14 @@ "CORION": "Corion", "CORN": "CORN", "CORNELLA": "CORNELLA", + "CORSI": "Cane Corso", "CORX": "CorionX", "COS": "Contentos", "COSHI": "CoShi Inu", "COSM": "CosmoChain", "COSMI": "Cosmic FOMO", "COSMIC": "CosmicSwap", + "COSMICN": "Cosmic Network", "COSP": "Cosplay Token", "COSS": "COS", "COST": "Costco Hot Dog", @@ -2780,6 +3216,7 @@ "CPRX": "Crypto Perx", "CPS": "Cryptostone", "CPT": "Cryptaur", + "CPTN": "Captain Max", "CPU": "CPUcoin", "CPX": "Apex Token", "CPY": "COPYTRACK", @@ -2794,12 +3231,16 @@ "CRADLE": "Cradle of Sins", "CRAFT": "TaleCraft", "CRAFTCOIN": "Craftcoin", + "CRAI": "Cryptify AI", "CRAIG": "CraigsCoin", "CRAMER": "Cramer Coin", "CRANEPAY": "Cranepay", "CRASH": "Solana Crash", + "CRASHBOYS": "CRASHBOYS", "CRAVE": "CraveCoin", "CRAYRABBIT": "CrazyRabbit", + "CRAZ": "CRAZY FLOKI", + "CRAZYB": "Crazy Bunny", "CRAZYBONK": "CRAZY BONK", "CRAZYBUNNY": "Crazy Bunny", "CRAZYCAT": "CRAZY CAT", @@ -2807,6 +3248,7 @@ "CRAZYDRAGON": "CRAZY DRAGON", "CRAZYMUSK": "CRAZY MUSK", "CRAZYPEPE": "CrazyPepe", + "CRAZYT": "CRAZY TRUMP", "CRAZYTIGER": "CRAZY TIGER", "CRB": "Creditbit", "CRBN": "Carbon", @@ -2846,10 +3288,14 @@ "CRGPT": "CryptoGPT", "CRH": "Crypto Hunters Coin", "CRHT": "CryptHub", + "CRI": "Criptodólar", "CRI3X": "CRI3X", "CRICKETS": "Kermit", "CRIME": "Crime Gold", "CRIMINGO": "Criminal Flamingo", + "CRIPPL": "Wheelchair Cat", + "CRIS": "CristianoRonaldoSpeedSmurf7Siu", + "CRISPR": "CRISPR", "CRK": "Croking", "CRL": "Cryptelo Coin", "CRM": "Cream", @@ -2878,6 +3324,7 @@ "CRS": "CRYSTALS", "CRSP": "CryptoSpots", "CRT": "Carr.Finance", + "CRTAI": "CRT AI Network", "CRTB": "Coritiba F.C. Fan Token", "CRTM": "Cryptum", "CRTS": "Cratos", @@ -2887,6 +3334,7 @@ "CRUMP": "Crypto Trump", "CRUX": "CryptoMines Reborn", "CRV": "Curve DAO Token", + "CRVE": "Curve DAO Token (Avalanche Bridge)", "CRVUSD": "crvUSD", "CRVY": "Curve Inu", "CRW": "Crown Coin", @@ -2898,12 +3346,26 @@ "CRYO": "CryoDAO", "CRYP": "CrypticCoin", "CRYPT": "CryptCoin", + "CRYPTER": "Crypteriumcoin", + "CRYPTOAI": "CryptoAI", + "CRYPTOB": "Crypto Burger", + "CRYPTOBEAST": "CryptoBeast", + "CRYPTOBL": "CryptoBlades Kingdoms", "CRYPTOBULLION": "CryptoBullion", "CRYPTOE": "Cryptoenter", + "CRYPTOEM": "Crypto Emperor Trump", + "CRYPTOF": "CryptoFarmers", + "CRYPTOH": "CryptoHunterTrading", + "CRYPTOJ": "Crypto Journey", "CRYPTON": "CRYPTON", "CRYPTONITE": "Cryptonite", + "CRYPTOOFFICIAL": "Crypto", + "CRYPTOPAL": "Pal", "CRYPTOPRO": "CryptoProfile", + "CRYPTOR": "CRYPTORG", + "CRYPTOS": "CryptoSoul", "CRYPTOSDG": "Crypto SDG", + "CRYPTOT": "Crypto Trump", "CRYPTOU": "CryptoUnity", "CRYSTAL": "Crystal", "CRYSTALCLEAR": "Crystal Clear Token", @@ -2915,6 +3377,7 @@ "CSC": "CasinoCoin", "CSEN": "Sentient Coin", "CSH": "CashOut", + "CSI": "CSI888", "CSIX": "Carbon Browser", "CSM": "Crust Shadow", "CSMIC": "Cosmic", @@ -2931,12 +3394,14 @@ "CSTL": "Castle", "CSTR": "CoreStarter", "CSUSHI": "cSUSHI", + "CSW": "Crosswalk", "CSWAP": "ChainSwap", "CSX": "Coinstox", "CT": "CryptoTwitter", "CTA": "Cross The Ages", "CTAG": "CTAGtoken", "CTASK": "CryptoTask", + "CTB": "Content Bitcoin", "CTC": "Creditcoin", "CTCN": "Contracoin", "CTE": "Crypto Tron", @@ -2951,12 +3416,13 @@ "CTLS": "Chaintools", "CTLX": "Cash Telex", "CTN": "Continuum Finance", - "CTO": "Crypto", + "CTO": "BaseCTO", "CTOK": "Codyfight", "CTP": "Ctomorrow Platform", "CTPL": "Cultiplan", "CTPT": "Contents Protocol", "CTR": "Creator Platform", + "CTRL": "Ctrl Wallet", "CTRL2XY": "Control2XY", "CTRT": "Cryptrust", "CTS": "Citrus", @@ -2979,7 +3445,9 @@ "CUEX": "Cuex", "CUFF": "Jail Cat", "CULO": "CULO", + "CULOETH": "CULO", "CULT": "Cult DAO", + "CULTUR": "Cultur", "CUM": "Cumbackbears", "CUMINU": "CumInu", "CUMMIES": "CumRocket", @@ -2988,6 +3456,7 @@ "CURE": "Curecoin", "CURI": "Curium", "CURIO": "Curio Governance", + "CURLY": "Curly", "CURR": "Curry", "CURRY": "CurrySwap", "CUSD": "Carbon", @@ -2997,6 +3466,7 @@ "CUST": "Custody Token", "CUT": "CUTcoin", "CUTE": "Blockchain Cuties Universe", + "CUUT": "CUTTLEFISHY", "CUZ": "Cool Cousin", "CV": "CarVertical", "CVA": "Crypto Village Accelerator", @@ -3046,11 +3516,13 @@ "CXO": "CargoX", "CXP": "Caixa Pay", "CXPAD": "CoinxPad", - "CXT": "Coinonat", + "CXT": "Covalent X Token", "CY97": "Cyclops97", "CYB": "CYBERTRUCK", "CYBA": "CYBRIA", + "CYBE": "Cyberlete", "CYBER": "CyberConnect", + "CYBERA": "Cyber Arena", "CYBERC": "CyberCoin", "CYBERD": "Cyber Doge", "CYBERTRUCK": "Cyber Truck", @@ -3065,6 +3537,7 @@ "CYCLUB": "Cyclub", "CYCON": "CONUN", "CYDER": "Cyder Coin", + "CYDX": "CyberDEX", "CYFI": "cYFI", "CYG": "Cygnus", "CYL": "Crystal Token", @@ -3073,13 +3546,16 @@ "CYOP": "CyOp Protocol", "CYP": "CypherPunkCoin", "CYPEPE": "CyPepe", + "CYPHER": "CYPHER•GENESIS (Runes)", "CYRS": "Cyrus Token", "CYRUS": "Cyrus Exchange", "CYS": "BlooCYS", "CYT": "Cryptokenz", + "CZ": "CHANGPENG ZHAO (changpengzhao.club)", "CZC": "Crazy Coin", "CZF": "CZodiac Farming Token", "CZGOAT": "CZ THE GOAT", + "CZKING": "CZKING", "CZOL": "Czolana", "CZR": "CanonChain", "CZRX": "Compound 0x", @@ -3094,6 +3570,7 @@ "D4RK": "DarkPayCoin", "DAAPL": "Apple Tokenized Stock Defichain", "DAB": "DABANKING", + "DABCAT": "Dabcat", "DAC": "Davinci Coin", "DACASH": "DACash", "DACAT": "daCat", @@ -3106,32 +3583,41 @@ "DAD": "DAD", "DADA": "DADA", "DADDY": "Daddy Tate", + "DADDYCHILL": "Daddy Chill", "DADDYDOGE": "Daddy Doge", "DADI": "Edge", + "DAETA": "DÆTA", "DAF": "DaFIN", "DAFI": "Dafi Protocol", "DAFT": "DaftCoin", "DAG": "Constellation", "DAGO": "Dago Mining", + "DAGS": "Dagcoin", "DAGT": "Digital Asset Guarantee Token", "DAI": "Dai", + "DAIE": "Dai (Avalanche Bridge)", "DAILY": "Coindaily", "DAILYS": "DailySwap Token", "DAIMO": "Diamond Token", "DAIN": "Dain Token", "DAIQ": "Daiquilibrium", "DAISY": "Daisy Launch Pad", + "DAK": "dak", "DAL": "DAOLaunch", "DALI": "Dalichain", + "DALMA": "Dalma Inu", "DAM": "Datamine", "DAMEX": "DAMEX", + "DAMN": "Sol Killer", "DAMO": "Coinzen", "DAMOON": "Damoon Coin", "DAN": "Daneel", "DANA": "Ardana", "DANG": "Guangdang", "DANGEL": "dAngel Fund", + "DANJ": "Danjuan Cat", "DANK": "DarkKush", + "DANNY": "Degen Danny", "DAO": "DAO Maker", "DAO1": "DAO1", "DAOACT": "ACT", @@ -3147,6 +3633,7 @@ "DAPS": "DAPS Coin", "DAR": "Mines of Dalarnia", "DARA": "Immutable", + "DARAM": "Daram", "DARB": "Darb Token", "DARC": "Konstellation", "DARCRUS": "Darcrus", @@ -3156,6 +3643,8 @@ "DARIK": "Darik", "DARK": "Dark", "DARKEN": "Dark Energy Crystals", + "DARKMAGACOIN": "DARK MAGA", + "DARKT": "Dark Trump", "DART": "dART Insurance", "DARX": "Bitdaric", "DAS": "DAS", @@ -3163,14 +3652,18 @@ "DASH": "Dash", "DASHD": "Dash Diamond", "DASHG": "Dash Green", + "DASIAv": "DASIA", "DAT": "Datum", "DATA": "Streamr", + "DATAO": "Data Ownership Protocol", "DATAWALLET": "DataWallet", + "DATOM": "Drop Staked ATOM", "DATP": "Decentralized Asset Trading Platform", "DATX": "DATx", "DAUMEN": "Daumenfrosch", "DAV": "DAV", "DAVE": "DAVE", + "DAVID": "David", "DAVINCI": "Davincigraph", "DAVIS": "Davis Cup Fan Token", "DAVP": "Davion", @@ -3196,7 +3689,7 @@ "DBL": "Doubloon", "DBOE": "DBOE", "DBOX": "DefiBox", - "DBR": "Düber", + "DBR": "deBridge", "DBTC": "DebitCoin", "DBTN": "Universa Native token", "DBUND": "DarkBundles", @@ -3213,6 +3706,10 @@ "DCB": "Decubate", "DCC": "Distributed Credit Chain", "DCCT": "DocuChain", + "DCD": "DecideAI", + "DCE": "Decentra Ecosystem", + "DCHEFSOL": "Degen Chef", + "DCHEWY": "Drop Chewy", "DCHF": "DeFi Franc", "DCI": "Decentralized Cloud Infrastructure", "DCIP": "Decentralized Community Investment Protocol", @@ -3234,15 +3731,18 @@ "DD": "DuckDAO", "DDAM": "DDAM", "DDAO": "DDAO Hunters", + "DDBAM": "Didi Bam Bam", "DDD": "Scry.info", "DDDD": "People's Punk", "DDF": "Digital Developers Fund", "DDIM": "DuckDaoDime", "DDK": "DDKoin", "DDL": "Donocle", + "DDMT": "Dongdaemun Token", "DDN": "Den Domains", "DDOS": "disBalancer", "DDR": "Digi Dinar", + "DDRO": "D-Drops", "DDRST": "DigiDinar StableToken", "DDRT": "DigiDinar Token", "DDS": "DDS.Store", @@ -3259,10 +3759,15 @@ "DECI": "Maximus DECI", "DECL": "Decimal token", "DECODE": "Decode Coin", + "DEDA": "DedaCoin", "DEDE": "Dede", + "DEDI": "Dedium", + "DEDPRZ": "DEDPRZ", + "DEEBO": "Deebo the Bear", "DEED": "Deed (Ordinals)", "DEEM": "iShares MSCI Emerging Markets ETF Defichain", - "DEEP": "DeepCloud AI", + "DEEP": "DeepBook Protocol", + "DEEPCLOUD": "DeepCloud AI", "DEEPG": "Deep Gold", "DEER": "ToxicDeer Finance", "DEEX": "DEEX", @@ -3290,6 +3795,7 @@ "DEGEN": "Degen", "DEGENR": "DegenReborn", "DEGO": "Dego Finance", + "DEGOD": "degod", "DEGOV": "Degov", "DEGOV1": "Dego Finance v1", "DEHUB": "DeHub", @@ -3300,6 +3806,7 @@ "DELFI": "DeltaFi", "DELI": "NFTDeli", "DELIGHTPAY": "DelightPay", + "DELON": "Dark Elon", "DELOT": "DELOT.IO", "DELTA": "Delta Financial", "DELTAC": "DeltaChain", @@ -3313,31 +3820,41 @@ "DEOD": "Decentrawood", "DEOR": "Decentralized Oracle", "DEP": "DEAPCOIN", + "DEPA": "Department Of Government Efficiency", + "DEPINU": "Depression Inu", "DEPO": "Depo", "DEPTH": "Depth Token", "DEQ": "Dequant", + "DER": "Deri Trade", "DERC": "DeRace", "DERI": "Deri Protocol", "DERO": "Dero", "DERP": "Derp", "DES": "DeSpace Protocol", + "DESCI": "DeSci Meme", "DESI": "Desico", "DESO": "Decentralized Social", "DESTINY": "Destiny", "DESU": "Dexsport", + "DESY": "Desy Duk", "DETENSOR": "DeTensor", "DETF": "Decentralized ETF", "DETH": "DarkEther", + "DETO": "Delta Exchange", "DEUR": "DigiEuro", "DEUS": "DEUS Finance", + "DEUSD": "Elixir deUSD", "DEV": "Deviant Coin", "DEVCOIN": "DevCoin", + "DEVE": "Develocity Finance", + "DEVI": "DEVITA", "DEVO": "DeVolution", "DEVT": "DeHorizon", "DEVVE": "Devve", "DEVX": "Developeo", "DEX": "DEX", "DEXA": "DEXA COIN", + "DEXC": "DexCoyote Legends", "DEXE": "DeXe", "DEXG": "Dextoken Governance", "DEXIO": "Dexioprotocol", @@ -3395,6 +3912,7 @@ "DGORE": "DogeGoreCoin", "DGP": "DGPayment", "DGPT": "DigiPulse", + "DGTA": "Digitra.com Token", "DGTX": "Digitex Token", "DGVC": "DegenVC", "DGX": "Digix Gold token", @@ -3407,10 +3925,12 @@ "DHV": "DeHive", "DHX": "DataHighway", "DIA": "DIA", + "DIAB": "Diablo IV Solana", "DIABLO": "Diablo IV", "DIAM": "Diamond", "DIAMND": "Projekt Diamond", "DIAMOND": "Diamond Coin", + "DIAMONDINU": "Diamond", "DIBBLE": "Dibbles", "DIBC": "DIBCOIN", "DIC": "Daikicoin", @@ -3420,6 +3940,7 @@ "DICK": "adDICKted", "DICKCOIN": "DickCoin", "DID": "Didcoin", + "DIDDY": "DIDDY", "DIDID": "Didi Duck", "DIE": "Die Protocol", "DIEM": "Facebook Diem", @@ -3434,9 +3955,12 @@ "DIGI": "Digiverse", "DIGIC": "DigiCube", "DIGIF": "DigiFel", + "DIGIT": "Digital Asset Rights Token", "DIGITAL": "Digital Reserve Currency", + "DIGITS": "Digits DAO", "DIGNITY": "Dignity", "DIGS": "Diggits", + "DIK": "DikDok", "DIKO": "Arkadiko", "DILI": "D Community", "DILIGENT": "Diligent Pepe", @@ -3445,15 +3969,18 @@ "DIME": "DimeCoin", "DIMO": "DIMO", "DIN": "Dinero", - "DINERO": "Dinerobet", + "DINERO": "Dinero", + "DINEROBET": "Dinerobet", "DINGER": "Dinger Token", "DINGO": "Dingocoin", "DINO": "DinoSwap", + "DINOS": "Dinosaur Inu", "DINT": "DinarTether", "DINU": "Dogey-Inu", "DINW": "Dinowars", "DIO": "Decimated", "DIONE": "Dione", + "DIONEV1": "Dione v1", "DIP": "Etherisc", "DIPA": "Doge Ipa", "DIRTY": "Dirty Street Cats", @@ -3461,6 +3988,7 @@ "DISCOVERY": "DiscoveryIoT", "DISK": "Dark Lisk", "DISPEPE": "Disabled Pepe", + "DISTR": "Distributed Autonomous Organization", "DIT": "Ditcoin", "DITH": "Dither AI", "DIVA": "DIVA Protocol", @@ -3471,6 +3999,7 @@ "DIW": "DIWtoken", "DIYAR": "Diyarbekirspor Token", "DJED": "Djed", + "DJI": "Doge Jones Industrial Average", "DJT": "Save America", "DK": "Dominant Kong", "DKA": "dKargo", @@ -3486,6 +4015,7 @@ "DLANCE": "DeeLance", "DLB": "DiemLibre", "DLC": "Diamond Launch", + "DLCBTC": "DLC.Link", "DLISK": "Dlisk", "DLLR": "Sovryn Dollar", "DLO": "Delio", @@ -3500,11 +4030,13 @@ "DLY": "Daily Finance", "DLYCOP": "Daily COP", "DMA": "Dragoma", + "DMAGA": "Dark MAGA", "DMAIL": "DMAIL Network", "DMAR": "DMarket", "DMC": "Dream21", "DMCC": "DiscoverFeed", "DMCH": "DARMA Cash", + "DMCK": "Diamond Castle", "DMD": "DMD", "DMG": "DMM: Governance", "DMGBULL": "3X Long DMM Governance Token", @@ -3538,11 +4070,14 @@ "DNXC": "DinoX", "DNY": "Dynasty Coin", "DNZ": "Denizlispor Fan Token", + "DOAI": "DOJO Protocol", "DOBBY": "Dobby", "DOBEN": "dark boden", "DOBO": "DogeBonk", "DOC": "Dochain", + "DOCAINEURON": "Doc.ai Neuron", "DOCC": "Doc Coin", + "DOCCOM": "DOC.COM", "DOCK": "Dock.io", "DOCSWAP": "Dex on Crypto", "DOCT": "DocTailor", @@ -3550,6 +4085,7 @@ "DOD100": "Day of Defeat Mini 100x", "DODI": "DoubleDice", "DODO": "DODO", + "DODOT": "Dodo the Black Swan", "DOE": "Dogs Of Elon", "DOFI": "Doge Floki Coin", "DOG": " DOG•GO•TO•THE•MOON", @@ -3558,15 +4094,19 @@ "DOGAI": "Dogai", "DOGALD": "dogald trump", "DOGB": "DogeBoy", + "DOGBA": "DOGBA INU", "DOGBOSS": "Dog Boss", "DOGC": "Dogeclub", + "DOGCOIN": "Dogcoin", "DOGDEFI": "DogDeFiCoin", "DOGE": "Dogecoin", "DOGE1SAT": "DOGE-1SATELLITE", + "DOGE2": "Dogecoin 2.0", "DOGE20": "Doge 2.0", "DOGEB": "DogeBonk", "DOGEBNB": "DogeBNB", "DOGEC": "DogeCash", + "DOGECAST": "Dogecast", "DOGECEO": "Doge CEO", "DOGECO": "Dogecolony", "DOGECOIN": "Buff Doge Coin", @@ -3575,17 +4115,24 @@ "DOGED": "DogeCoinDark", "DOGEDAO": "DogeDao", "DOGEDASH": "Doge Dash", + "DOGEFA": "DOGEFATHER", + "DOGEFATHER": "Dogefather", "DOGEFORK": "DogeFork", "DOGEGF": "DogeGF", "DOGEGROK": "Doge Grok", "DOGEGROKAI": "Doge Of Grok AI", + "DOGEI": "Dogei", + "DOGEIN": "Doge In Glasses", "DOGEKING": "DogeKing", "DOGELEGION": "DOGE LEGION", + "DOGEM": "Doge Matrix", "DOGEMETA": "Dogemetaverse", "DOGEMOB": "DOGEMOB", "DOGENFT": "The Doge NFT", "DOGEP": "Doge Protocol", "DOGEPAY": "Doge Payment", + "DOGEPEPE": "Doge Pepe", + "DOGEPR": "DOGE PRESIDENT", "DOGER": "Robotic Doge", "DOGERA": "Dogera", "DOGES": "Dogeswap", @@ -3595,46 +4142,62 @@ "DOGEWHALE": "Dogewhale", "DOGEX": "DogeHouse Capital", "DOGEY": "Dogey", + "DOGEYIELD": "DogeYield", "DOGEZILLA": "DogeZilla", + "DOGEZILLAV1": "DogeZilla v1", + "DOGG": "Doggo", "DOGGS": "Doggensnout", "DOGGY": "Doggy", + "DOGGYCOIN": "DOGGY", "DOGH": "a dog in a hoodie", "DOGI": "dogi", "DOGIN": "Doginhood", "DOGINC": "dog in cats world", "DOGINME": "doginme", "DOGIRA": "Dogira", + "DOGK": "Dagknight Dog", + "DOGLAI": "Doglaikacoin", "DOGMI": "DOGMI", "DOGO": "DogemonGo", "DOGPAD": "DogPad Finance", "DOGRMY": "DogeArmy", - "DOGS": "Dogcoin", + "DOGS": "Dogs", "DOGSROCK": "Dogs Rock", + "DOGSS": "DOGS SOL", + "DOGSSO": "DOGS Solana", "DOGSWAG": "DogSwaghat", + "DOGW": "DOGWIFHOOD", "DOGWIFHAT": "dogwifhat", "DOGWIFSEAL": "dogwifseal", - "DOGY": "DogeYield", + "DOGY": "Dogy", "DOGZ": "Dogz", "DOJO": "ProjectDojo", "DOKI": "Doki Doki Finance", "DOKY": "Donkey King", "DOLA": "Dola USD Stablecoin", + "DOLAN": "Dolan Duck", + "DOLLAR": "Dollar", "DOLLARCOIN": "DollarCoin", "DOLLUR": "Dollur Go Brrr", + "DOLPHY": "Dolphy", "DOLZ": "DOLZ", "DOM": "Ancient Kingdom", "DOME": "Everdome", "DOMI": "Domi", + "DOMO": "Dony Montana", "DON": "Donnie Finance", "DONA": "DONASWAP", + "DONAL": "Donald Pump", "DONALD": "DONALD TRUMP", "DONALDT": "Donald The Trump", "DONATION": "DonationCoin", "DONG": "DongCoin", "DONGO": "Dongo AI", + "DONJR": "Don Jr.", "DONK": "Don-key", "DONKE": "DONKE", "DONS": "The Dons", + "DONT": "Donald Trump (dont.cash)", "DONU": "Donu", "DONUT": "Donut", "DONUTS": "The Simpsons", @@ -3643,7 +4206,9 @@ "DOOH": "Bidooh", "DOOMER": "Doomer", "DOOR": "DOOR", + "DOPA": "DopaMeme", "DOPE": "Dopamine App", + "DOPEC": "DOPE Coin", "DOPECOIN": "DopeCoin", "DOPU": "DOPU The Dog with A Purpose", "DOR": "Dorado", @@ -3651,17 +4216,20 @@ "DORAV1": "Dora Factory v1", "DORK": "DORK", "DORKL": "DORK LORD", + "DORKVADER": "DorkVader", "DORKY": "Dork Lord", "DOS": "DOS Network", "DOSE": "DOSE", "DOSHIB": "DogeShiba", "DOT": "Polkadot", "DOTC": "Dotcoin", + "DOTF": "Dot Finance", "DOTR": "Cydotori", "DOUG": "Doug The Duck", "DOUGH": "PieDAO v2 (DOUGH)", "DOV": "DOVU", "DOVI": "Dovi(Ordinals)", + "DOVIS": "Dovish Finance", "DOVU": "DOVU", "DOWS": "Shadows", "DP": "DigitalPrice", @@ -3676,6 +4244,7 @@ "DPLN": "DePlan", "DPLTR": "Palantir Tokenized Stock Defichain", "DPN": "DIPNET", + "DPOOL": "Deadpool Inu", "DPP": "Digital Assets Power Play", "DPR": "Deeper Network", "DPS": "DEEPSPACE", @@ -3694,8 +4263,10 @@ "DRAGONGROK": "DragonGROK", "DRAGONKING": "DragonKing", "DRAGONMA": "Dragon Mainland Shards", + "DRAGONX": "DragonX", "DRAGU": "DRAGU", "DRAGY": "Dragy", + "DRAKO": "Drako", "DRAM": "DRAM", "DRAW": "Drawshop Kingdom Reverse", "DRB": "Digimon Rabbit", @@ -3730,6 +4301,7 @@ "DRXNE": "Droxne", "DRZ": "Droidz", "DS": "DeStorage", + "DSAI": "DeSend Ai", "DSB": "DarkShibe", "DSC": "Dash Cash", "DSCP": "Dreamscape", @@ -3747,6 +4319,8 @@ "DSR": "Desire", "DSRUN": "Derby Stars", "DST": "Double Swap Token", + "DSTAG": "deadstag", + "DSTNY": "Destinys Chicken", "DSTR": "Dynamic Supply Tracker", "DSUN": "DsunDAO", "DSYNC": "Destra Network", @@ -3756,6 +4330,7 @@ "DTB": "Databits", "DTC": "Data Transaction", "DTCT": "DetectorToken", + "DTEC": "Dtec", "DTEM": "Dystem", "DTEP": "DECOIN", "DTG": "Defi Tiger", @@ -3768,6 +4343,7 @@ "DTORO": "DexToro", "DTR": "Dotori", "DTRC": "Datarius", + "DTRUMP": "Degen Trump", "DTSLA": "Tesla Tokenized Stock Defichain", "DTX": "DataBroker DAO", "DUA": "Brillion", @@ -3775,15 +4351,18 @@ "DUB": "DubCoin", "DUBAICAT": "Dubai Cat", "DUBBZ": "Dubbz", + "DUBER": "Düber", "DUBI": "Decentralized Universal Basic Income", "DUBX": "DUBXCOIN", "DUC": "DucatusCoin", "DUCATO": "Ducato Protocol Token", "DUCK": "Unit Protocol New", + "DUCKC": "DuckCoin", "DUCKD": "DuckDuckCoin", "DUCKER": "Ducker", "DUCKIES": "Yellow Duckies", "DUCKO": "Duck Off Coin", + "DUCKY": "Ducky Duck", "DUCX": "DucatusX", "DUDE": "DuDe", "DUEL": "GameGPT", @@ -3802,6 +4381,7 @@ "DUO": "ParallelCoin", "DUOT": "DUO Network", "DUREV": "Povel Durev", + "DUROV": "FREE DUROV", "DURTH": "iShares MSCI World ETF Tokenized Stock Defichain", "DUSD": "Decentralized USD", "DUSK": "Dusk Network", @@ -3816,6 +4396,7 @@ "DVI": "Dvision Network", "DVINCI": "Davinci Jeremie", "DVK": "Devikins", + "DVL": "Develad", "DVNQ": "Vanguard Real Estate Tokenized Stock Defichain ()", "DVOO": "Vanguard S&P 500 ETF Tokenized Stock Defichain", "DVP": "Decentralized Vulnerability Platform", @@ -3825,9 +4406,12 @@ "DVT": "DeVault", "DVTC": "DivotyCoin", "DVX": "Derivex", + "DWARFY": "Dwarfy", "DWARS": "Dynasty Wars", "DWC": "Digital Wallet", "DWEB": "DecentraWeb", + "DWOG": "DWOG THE DOG", + "DWOLF": "Dark Wolf", "DWT": "DiveWallet Token", "DWZ": "DeFi Wizard", "DX": "DxChain Token", @@ -3846,16 +4430,20 @@ "DXR": "DEXTER", "DXS": "Dx Spot", "DXT": "Dexit Finance", + "DXY": "US Degen Index 6900", + "DYAD": "Dyad Stable", "DYC": "Dycoin", "DYDX": "dYdX", "DYM": "Dymension", "DYN": "Dynamic", "DYNA": "Dynamix", + "DYNAM": "Dynamic Crypto Index", "DYNAMICTRADING": "Dynamic Trading Rights", "DYNCOIN": "Dyncoin", "DYNEX": "Dynex GPU", "DYNMT": "Dynamite", "DYNO": "DYNO", + "DYNOC": "DynoChain", "DYOR": "DYOR Token", "DYP": "Dypius", "DYPV1": "Dypius v1", @@ -3869,26 +4457,32 @@ "DZI": "DeFinition", "DZOO": "Degen Zoo", "Dow": "DowCoin", + "E1INCH": "1inch (Energi Bridge)", "E21": "E21 Coin", "E2C": "Electronic Energy Coin", "E8": "Energy8", "EA": "EagleCoin", "EAC": "Education Assessment Cult", + "EADX": "EADX Token", "EAG": "Emerging Assets Group", "EAGLE": "Eagle Token", "EAGS": "EagsCoin", - "EAI": "Edain", + "EAI": "Eagle AI", + "EARLY": "Early Risers", + "EARLYF": "EarlyFans", "EARN": "EarnGuild", "EARTH": "Earth Token", "EARTHCOIN": "EarthCoin", "EASYF": "EasyFeedback", "EAT": "EDGE Activity Token", + "EATH": "Eartherium", "EAURIC": "Eauric", "EAVE": "EaveAI", "EB3": "EB3coin", "EBA": "Elpis Battle", "EBASE": "EURBASE", "EBC": "EBCoin", + "EBCH": "Bitcoin Cash (Energiswap)", "EBEN": "Green Ben", "EBET": "EthBet", "EBIT": "eBit", @@ -3896,10 +4490,12 @@ "EBOX": "Ethbox Token", "EBS": "EbolaShare", "EBSC": "EarlyBSC", + "EBSHIB": "Wrapped Energy Shiba Inu (Energi Bridge)", "EBSO": "eBlockStock", "EBST": "eBoost", "EBT": "ELON BUYS TWITTER", "EBTC": "eBitcoin", + "EBULL": "ETHEREUM IS GOOD", "EBYT": "EarthByt", "EBZ": "Ebitz", "EC": "Echoin", @@ -3911,7 +4507,7 @@ "ECET": "Evercraft Ecotechnologies", "ECG": "EcoSmart", "ECH": "EthereCash", - "ECHO": "ECHO BOT", + "ECHOBOT": "ECHO BOT", "ECHOD": "EchoDEX", "ECHT": "e-Chat", "ECI": "Euro Cup Inu", @@ -3934,6 +4530,8 @@ "ECT": "SuperEdge", "ECTE": "EurocoinToken", "ECU": "ECOSC", + "ECXX": "ECXX", + "EDAIN": "Edain", "EDAT": "EnviDa", "EDC": "EDC Blockchain", "EDDA": "EDDASwap", @@ -3952,6 +4550,7 @@ "EDLC": "Edelcoin", "EDN": "EdenChain", "EDNS": "EDNS Token", + "EDOG": "EDOG", "EDOGE": "ElonDoge", "EDR": "Endor Protocol Token", "EDRC": "EDRCoin", @@ -3973,6 +4572,7 @@ "EFIL": "Ethereum Wrapped Filecoin", "EFK": "ReFork", "EFL": "E-Gulden", + "EFR": "End Federal Reserve", "EFT": "ETH Fan Token Ecosystem", "EFX": "The Effect.ai", "EG": "EG Token", @@ -3990,6 +4590,7 @@ "EGGP": "Eggplant Finance", "EGGY": "EGGY", "EGI": "eGame", + "EGL": "The Eagle Of Truth", "EGLD": "eGold", "EGO": "EGOcoin", "EGOD": "EgodCoin", @@ -4000,19 +4601,27 @@ "EGS": "EdgeSwap", "EGT": "Egretia", "EGX": "Enegra", + "EGY": "Egypt Cat", "EHASH": "EHash", "EHIVE": "eHive", "EHRT": "Eight Hours Token", "EIFI": "EIFI FINANCE", + "EIGEN": "EigenLayer", "EIM": "Expert Infra", + "EIQT": "IQ Prediction", "EJAC": "EJA Coin", "EJS": "Enjinstarter", "EKG": "Ekon Gold", "EKN": "Elektron", "EKO": "EchoLink", + "EKOC": "Coke", "EKS": "Elumia Krystal Shards", + "EKSM": "Synthetic Kusama (Energiswap)", "EKT": "EDUCare", "EKTA": "Ekta", + "EKTAV1": "Ekta v1", + "EKTAV2": "Ekta v2", + "EKUBO": "Ekubo Protocol", "EL": "ELYSIA", "ELA": "Elastos", "ELAC": "ELA Coin", @@ -4042,11 +4651,14 @@ "ELITE": "EthereumLite", "ELIX": "Elixir", "ELIXIR": "Starchi", + "ELIZ": "Eliza (ai16zeliza)", + "ELIZA": "Eliza (elizawakesup.ai)", "ELK": "Elk Finance", "ELLA": "Ellaism", "ELLI": "ElliotCoin", "ELM": "Elements Play", "ELMO": "ELMOERC", + "ELMOCOIN": "Elmo", "ELMON": "Elemon", "ELMT": "Element", "ELO": "ElonPark", @@ -4054,15 +4666,21 @@ "ELON2024": "ELON 2024(BSC)", "ELON404": "Elon404", "ELONCAT": "ELON CAT COIN", + "ELOND": "ELON DOGE", "ELONDOGE": "ELON DOGE", "ELONDRAGON": "ELON DRAGON", + "ELONGATE": "ElonGate", "ELONGD": "Elongate Deluxe", "ELONGT": "Elon GOAT", + "ELONIA": "Elonia Trump", "ELONIUM": "Elonium", "ELONM": "ELON MEME", + "ELONMA": "ELON MARS", "ELONMARS": "ELON MARS", + "ELONMU": "Elon Musk", "ELONONE": "AstroElon", "ELONPEPE": "Elon Pepe Robot", + "ELONTRUMP": "ELON TRUMP", "ELP": "Ellerium", "ELS": "Ethlas", "ELT": "Element Black", @@ -4080,6 +4698,7 @@ "EMAID": "MaidSafeCoin", "EMANATE": "EMANATE", "EMAR": "EmaratCoin", + "EMATIC": "Wrapped Polygon (Energi Bridge)", "EMAX": "EthereumMax", "EMB": "Overline Emblem", "EMBER": "EmberCoin", @@ -4120,15 +4739,18 @@ "ENDCEX": "Endpoint CeX Fan Token", "ENDLESS": "Endless Board Game", "ENE": "EneCoin", + "ENEAR": "Near (Energiswap)", "ENEDEX": "Enedex", "ENERGYX": "Safe Energy", "ENG": "Enigma", "ENGT": "Engagement Token", "ENIGMA": "ENIGMA", "ENJ": "Enjin Coin", + "ENJV1": "Enjin Coin v1", "ENK": "Enkidu", "ENNO": "ENNO Cash", "ENO": "Enotoken", + "ENOKIFIN": "Enoki Finance", "ENQ": "Enecuum", "ENQAI": "enqAI", "ENRG": "EnergyCoin", @@ -4182,7 +4804,8 @@ "EQT": "EquiTrader", "EQU": "Equation", "EQUAD": "Quadrant Protocol", - "EQUAL": "EqualCoin", + "EQUAL": "Equalizer DEX", + "EQUALCOIN": "EqualCoin", "EQUI": "EQUI", "EQUIL": "Equilibrium", "EQUITOKEN": "EQUI Token", @@ -4230,11 +4853,13 @@ "ESN": "Ethersocial", "ESNC": "Galaxy Arena Metaverse", "ESP": "Espers", + "ESPL": "ESPL ARENA", "ESPR": "Espresso Bot", "ESRC": "ESR Coin", "ESS": "Essentia", "EST": "ESports Chain", "ESTATE": "AgentMile", + "ESTEE": "Kaga No Fuuka Go Sapporo Kagasou", "ESW": "eSwitch®", "ESZ": "EtherSportz", "ET": "ENDO", @@ -4257,11 +4882,17 @@ "ETH2X-FLI": "ETH 2x Flexible Leverage Index", "ETHA": "ETHA Lend", "ETHAX": "ETHAX", - "ETHB": "EtherBTC", + "ETHB": "ETHEREUM ON BASE", "ETHBN": "EtherBone", "ETHD": "Ethereum Dark", + "ETHDOG": "Ethereumdog", "ETHER": "Etherparty", + "ETHERBTC": "EtherBTC", "ETHERDELTA": "EtherDelta", + "ETHERE": "Ethereal", + "ETHEREM": "Etherempires", + "ETHEREUM": "Solana Ethereum Meme", + "ETHEREUMP": "ETHEREUMPLUS", "ETHERINC": "EtherInc", "ETHERKING": "Ether Kingdoms Token", "ETHERNITY": "Ethernity Chain", @@ -4310,21 +4941,32 @@ "EUC": "Eurocoin", "EUCOIN": "EU Coin", "EUCX": "EUCX", + "EUD": "Eurodom", "EUL": "Euler", "EULER": "Euler Tools", "EUM": "Elitium", "EUNO": "EUNO", "EURC": "Euro Coin", + "EURCV": "EUR CoinVertible", + "EURCVV1": "EUR CoinVertible v1", "EURE": "Monerium EUR emoney", + "EURI": "Eurite", "EURN": "NOKU EUR", + "EUROCUP": "EURO CUP INU", "EUROE": "EUROe Stablecoin", + "EUROP": "Europa Coin", + "EURQ": "Quantoz EURQ", + "EURR": "StablR Euro", "EURS": "STASIS EURS", "EURT": "Euro Tether", + "EURTV1": "Euro Tether v1", "EURU": "Upper Euro", "EURX": "eToro Euro", "EUSD": "Egoras Dollar", + "EUTBL": "Spiko EU T-Bills Money Market Fund", "EV": "EVAI", "EVA": "Evadore", + "EVAI": "EVA Intelligence", "EVAN": "Evanesco Network", "EVAULT": "EthereumVault", "EVC": "Eventchain", @@ -4342,9 +4984,11 @@ "EVERGREEN": "EverGreenCoin", "EVERLIFE": "EverLife.AI", "EVERMOON": "EverMoon", + "EVERV": "EverValue Coin", "EVERY": "Everyworld", "EVIL": "EvilCoin", "EVILPEPE": "Evil Pepe", + "EVIN": "Evin Token", "EVMOS": "Evmos", "EVN": "Evn Token", "EVO": "EvoVerses", @@ -4388,6 +5032,7 @@ "EXO": "Exosis", "EXOS": "Exobots", "EXP": "Expanse", + "EXPAND": "Gems", "EXPO": "Exponential Capital", "EXRD": "Radix", "EXRN": "EXRNchain", @@ -4402,9 +5047,12 @@ "EYETOKEN": "EYE Token", "EZ": "EasyFi V2", "EZC": "EZCoin", + "EZEIGEN": "Restaked EIGEN", "EZETH": "Renzo Restaked ETH", "EZI": "Ezillion", "EZM": "EZMarket", + "EZPZ": "Eazy Peazy", + "EZSOL": "Renzo Restaked SOL", "EZT": "EZToken", "EZY": "EzyStayz", "ElvishMagic": "EMAGIC", @@ -4423,9 +5071,11 @@ "FACETER": "Faceter", "FACT": "Orcfax", "FACTOM": "Factom", + "FACTORY": "ChainFactory", "FACTR": "Defactor", "FADO": "FADO Go", "FAG": "PoorFag", + "FAH": "Falcons", "FAI": "Fairum", "FAIR": "FairCoin", "FAIRC": "Faireum Token", @@ -4435,9 +5085,11 @@ "FAKT": "Medifakt", "FALCONS": "Falcon Swaps", "FALX": "FalconX", + "FAM": "Family", "FAME": "Fame MMA", "FAMEC": "FameCoin", "FAMILY": "The Bitcoin Family", + "FAML": "FAML", "FAMOUSF": "Famous Fox Federation", "FAN": "Fanadise", "FAN360": "Fan360", @@ -4447,19 +5099,25 @@ "FANV": "FanVerse", "FANX": "FrontFanz", "FANZ": "FanChain", + "FAPTAX": "Faptax", "FAR": "Farmland Protocol", "FARA": "FaraLand", "FARCA": "Farcana", "FARM": "Harvest Finance", "FARMA": "FarmaTrust", "FARMC": "FARM Coin", + "FARME": "Farmers Only", "FARMING": "Farming Bad", "FARMS": "Farmsent", + "FARTCOIN": "Fartcoin", + "FAS": "fast construction coin", "FAST": "Fastswap", + "FASTAI": "Fast And Ai", "FASTMOON": "FastMoon", "FASTV1": "Fastswap v1", "FAT": "Fatcoin", "FATCAKE": "FatCake", + "FATH": "Father Of Meme: Origin", "FATHER": "DogeFather", "FATHOM": "Fathom", "FATMICHI": "FATMICHI", @@ -4474,6 +5132,8 @@ "FBG": "Fort Block Games", "FBN": "Five balance", "FBNB": "ForeverBNB", + "FBOMB": "fBomb", + "FBOMBV1": "fBomb v1", "FBURN": "Forever Burn", "FBX": "Finance Blocks", "FC": "Facecoin", @@ -4503,7 +5163,9 @@ "FDX": "fidentiaX", "FDZ": "Friendz", "FEAR": "Fear", + "FEARNOT": "FEAR NOT", "FECES": "FECES", + "FEE": "FEED EVERY GORILLA", "FEED": "Feeder Finance", "FEENIXV2": "ProjectFeenixv2", "FEES": "UNIFEES", @@ -4522,8 +5184,9 @@ "FERT": "Chikn Fert", "FERZAN": "Ferzan", "FESS": "Fesschain", - "FET": "Fetch.AI", + "FET": "Artificial Superintelligence Alliance", "FETCH": "Fetch", + "FETS": "FE TECH", "FEVR": "RealFevr", "FEX": "FEX Token", "FEY": "Feyorra", @@ -4534,6 +5197,7 @@ "FFCT": "FortFC", "FFM": "Files.fm Library", "FFN": "Fairy Forest", + "FFTP": "FIGHT FOR THE PEOPLE", "FFUEL": "getFIFO", "FFYI": "Fiscus FYI", "FGC": "FantasyGold", @@ -4558,7 +5222,11 @@ "FIF": "flokiwifhat", "FIFTY": "FIFTYONEFIFTY", "FIG": "FlowCom", + "FIGH": "FIGHT FIGHT FIGHT", "FIGHT": "Crypto Fight Club", + "FIGHTMAGA": "FIGHT MAGA", + "FIGHTPEPE": "FIGHT PEPE", + "FIGHTRUMP": "FIGHT TRUMP", "FIH": "Fidelity House", "FIII": "Fiii", "FIL": "FileCoin", @@ -4570,9 +5238,11 @@ "FIN": "DeFiner", "FINA": "Defina Finance", "FINALE": "Ben's Finale", + "FINAN": "FINANCIAL TRANSACTION SYSTEM", "FINB": "Finblox", "FINC": "Finceptor", "FIND": "FindCoin", + "FINDER": "Finder AI", "FINE": "Refinable", "FINGER": "Finger Blast", "FINK": "FINK", @@ -4582,15 +5252,20 @@ "FINT": "FintraDao", "FINU": "Formula Inu", "FIO": "FIO Protocol", + "FIONA": "Fiona", + "FIONABSC": "Fiona", "FIRA": "Defira", "FIRE": "Matr1x Fire", "FIRECOIN": "FireCoin", + "FIREW": "Fire Wolf", "FIRO": "Firo", "FIRSTHARE": "FirstHare", "FIRU": "Firulais Finance", "FIS": "Stafi", "FISH": "Polycat Finance", - "FIST": "FistBump", + "FISHK": "Fishkoin", + "FIST": "Fistbump", + "FISTBUMP": "FistBump", "FIT": "Financial Investment Token", "FITC": "Fitcoin", "FITFI": "Step App", @@ -4610,17 +5285,23 @@ "FKSK": "Fatih Karagümrük SK", "FKX": "FortKnoxster", "FL": "Freeliquid", + "FLA": "Flappy", "FLAG": "Flag Network", + "FLAKY": "FLAKY", "FLAME": "FireStarter", "FLAP": "Flappy Coin", "FLAPPY": "Flappy", + "FLAREF": "FlareFoxInu", "FLAS": "Flas Exchange Token", "FLASH": "Flashstake", "FLASHC": "FLASH coin", + "FLAVIA": "Flavia Is Online", + "FLAY": "Flayer", "FLC": "FlowChainCoin", "FLD": "FluidAI", "FLDC": "Folding Coin", "FLDT": "FairyLand", + "FLEA": "FLEABONE", "FLEPE": "Floki VS Pepe", "FLETA": "FLETA", "FLEX": "FLEX Coin", @@ -4665,6 +5346,7 @@ "FLOVI": "Flovi inu", "FLOVM": "FLOV MARKET", "FLOW": "Flow", + "FLOWER": "FlowerAI", "FLOWP": "Flow Protocol", "FLOYX": "Floyx", "FLP": "Gameflip", @@ -4673,7 +5355,10 @@ "FLRS": "Flourish Coin", "FLS": "Flits", "FLT": "Fluence", + "FLUFFI": "Fluffington", + "FLUFFY": "FLUFFY", "FLUFFYS": "Fluffys", + "FLUI": "Fluidity", "FLUID": "Fluid", "FLURRY": "Flurry Finance", "FLUT": "Flute", @@ -4709,9 +5394,11 @@ "FNLX": "Fignal X", "FNO": "Fonero", "FNP": "FlipNpik", + "FNS": "FAUNUS", "FNSA": "FINSCHIA", "FNTB": "FinTab", "FNX": "FinNexus", + "FNXAI": "Finanx AI", "FNZ": "Fanzee", "FO": "FIBOS", "FOA": "Fragments of arker", @@ -4720,14 +5407,18 @@ "FOCV": "FOCV", "FODL": "Fodl Finance", "FOF": "Future Of Fintech", - "FOFAR": "Fofar", + "FOFAR": "FoFar", + "FOFARBASE": "FOFAR", + "FOFARIO": "Fofar", "FOFO": "FOFO Token", "FOGE": "Fat Doge", "FOIN": "Foin", "FOL": "Folder Protocol", "FOLD": "Manifold Finance", "FOLO": "Alpha Impact", - "FOMO": "Aavegotchi FOMO", + "FOMO": "FOMO BULL CLUB", + "FOMON": "FOMO Network", + "FOMOSOL": "FOMOSolana", "FON": "INOFI", "FONE": "Fone", "FONS": "FONSmartChain", @@ -4743,6 +5434,7 @@ "FORCE": "TriForce Tokens", "FORCEC": "Force Coin", "FORE": "FORE Protocol", + "FOREST": "FOREST", "FORESTPLUS": "The Forbidden Forest", "FOREVER": "Forever Coin", "FOREVERFOMO": "ForeverFOMO", @@ -4771,9 +5463,11 @@ "FOXE": "Foxe", "FOXF": "Fox Finance", "FOXGIRL": "FoxGirl", + "FOXI": "Foxify", "FOXSY": "Foxsy AI", "FOXT": "Fox Trading", "FOXV2": "FoxFinanceV2", + "FOXXY": "FOXXY", "FOXY": "Foxy", "FP": "Fren Pet", "FPAD": "FantomPAD", @@ -4786,6 +5480,7 @@ "FR": "Freedom Reserve", "FRA": "Findora", "FRAC": "FractalCoin", + "FRATT": "Frogg and Ratt", "FRAX": "Frax", "FRAZ": "FrazCoin", "FRBK": " FreeBnk", @@ -4793,6 +5488,7 @@ "FRD": "Farad", "FRDX": "Frodo Tech", "FRE": "FreeCoin", + "FREAK": "Freakoff", "FREC": "Freyrchain", "FRECNX": "FreldoCoinX", "FRED": "FREDEnergy", @@ -4800,8 +5496,11 @@ "FREE": "FREE coin", "FREED": "FreedomCoin", "FREEDO": "Freedom", + "FREEDOM": "Freedom Protocol Token", "FREELA": "DecentralFree", + "FREEPAVEL": "Free Pavel", "FREEROSS": "FreeRossDAO", + "FREET": "FreeTrump", "FREL": "Freela", "FREN": "FREN", "FRENCH": "French On Base", @@ -4828,8 +5527,12 @@ "FROGGY": "Froggy", "FROGLIC": "Pink Hood Froglicker", "FROGO": "Frogo", + "FROK": "Frok.ai", "FRONK": "Fronk", "FRONT": "Frontier", + "FROP": "Popo The Frog", + "FROSTY": "Frosty the Polar Bear", + "FROX": "Frox", "FROYO": "Froyo Games", "FROZE": "FrozenAi", "FRP": "Fame Reward Plus", @@ -4852,6 +5555,7 @@ "FSHN": "Fashion Coin", "FSM": "Floki SafeMoon", "FSN": "Fusion", + "FSNV1": "Fusion v1", "FSO": "FSociety", "FST": "Futureswap", "FSTC": "FastCoin", @@ -4859,6 +5563,7 @@ "FT": "Fracton Protocol", "FTB": "Fit&Beat", "FTC": "FeatherCoin", + "FTD": "42DAO", "FTG": "fantomGO", "FTH": "Fintyhub Token", "FTHM": "Fathom Protocol", @@ -4868,6 +5573,7 @@ "FTMO": "Fantom Oasis", "FTN": "Fasttoken", "FTO": "FuturoCoin", + "FTON": "Fanton", "FTP": "FuturePoints", "FTR": "FactR", "FTRB": "Faith Tribe", @@ -4875,18 +5581,22 @@ "FTS": "Fortress Lending", "FTT": "FTX Token", "FTTOKEN": "Finance Token", + "FTTT": "FTT Token", "FTUM": "Fatum", "FTVT": "FashionTV Token", "FTW": "FutureWorks", "FTX": "FintruX", "FTXT": "FUTURAX", + "FU": "FU Money", "FUBAO": "FUBAO", "FUCK": "Fuck Token", "FUD": "FUD.finance", "FUEL": "Jetfuel Finance", + "FUELX": "Fuel", "FUFU": "Fufu Token", + "FUG": "FUG", "FUJIN": "Fujinto", - "FUKU": "Furukuru", + "FUKU": "FUKU-KUN", "FUL": "Fulcrom Finance", "FUMO": "Alien Milady Fumo", "FUN": "FUN Token", @@ -4906,6 +5616,7 @@ "FUR": "Furio", "FURIE": "Matt Furie", "FURU": "Furucombo", + "FURUKURU": "Furukuru", "FURY": "Engines of Fury", "FURYX": "Metafury", "FUS": "Fus", @@ -4914,7 +5625,9 @@ "FUSE": "Fuse Network Token", "FUSION": "FusionBot", "FUSO": "Fusotao", + "FUT": "FuturesAI", "FUTC": "FutCoin", + "FUTUR": "Future Token", "FUTURE": "FutureCoin", "FUTUREAI": "Future AI", "FUZE": "FUZE Token", @@ -4925,6 +5638,7 @@ "FWB": "Friends With Benefits Pro", "FWC": "Qatar 2022", "FWH": "FigureWifHat", + "FWOG": "Fwog", "FWT": "Freeway Token", "FWW": "Farmers World Wood", "FX": "Function X", @@ -4938,13 +5652,15 @@ "FXS": "Frax Share", "FXST": "FX Stock Token", "FXT": "FuzeX", + "FXUSD": "f(x) Protocol fxUSD", "FXY": "Floxypay", "FYD": "FYDcoin", + "FYDO": "Fly Doge", "FYN": "Affyn", "FYP": "FlypMe", "FYZ": "Fyooz", "FYZNFT": "Fyooz NFT", - "G": "GRN Grid", + "G": "Gravity", "G1X": "GoldFinX", "G3": "GAM3S.GG", "G50": "G50", @@ -4976,21 +5692,29 @@ "GALT": "Galtcoin", "GAM": "Gambit coin", "GAMB": "GAMB", + "GAMBI": "Gambi Fi", "GAMBIT": "Gambit", "GAMBL": "Metagamble", "GAME": "GameBuild", "GAMEBUD": "GAMEBUD", "GAMEC": "Game", + "GAMECO": "Game.com", "GAMECRED": "GameCredits", + "GAMEF": "Game Fantasy Token", "GAMEFI": "GameFi Token", "GAMEFORK": "GameFork", "GAMEIN": "Game Infinity", "GAMER": "GameStation", "GAMERFI": "GamerFI", "GAMES": "Gamestarter", + "GAMEST": "GameStop Coin", "GAMESTARS": "Game Stars", + "GAMESTO": "GameStop", + "GAMESTUMP": "GAMESTUMP", + "GAMET": "GAME Token", "GAMEX": "GameX", "GAMI": "GAMI World", + "GAMIN": "Gaming Stars", "GAMINGDOGE": "GAMINGDOGE", "GAMINGSHIBA": "GamingShiba", "GAMMA": "Gamma Strategies", @@ -5020,6 +5744,7 @@ "GATSBY": "Gatsby Inu", "GAU": "Gamer Arena", "GAUSS": "Gauss0x", + "GAY": "GAY", "GAYPEPE": "Gay Pepe", "GAYSLER": "Gaysler", "GAZE": "GazeTV", @@ -5052,6 +5777,7 @@ "GCAT": "Giga Cat on Base", "GCB": "Global Commercial Business", "GCC": "GuccioneCoin", + "GCCO": "GCCOIN", "GCME": "GoCryptoMe", "GCN": "gCn Coin", "GCOIN": "Galaxy Fight Club", @@ -5066,6 +5792,7 @@ "GDE": "Golden Eagle", "GDL": "GodlyCoin", "GDO": "GroupDao", + "GDOG": "GDOG", "GDOGE": "Golden Doge", "GDR": "Guider.Travel", "GDRT": "Good Driver Reward Token", @@ -5078,7 +5805,10 @@ "GEAR": "Gearbox Protocol", "GEC": "Geco.one", "GECKO": "Gecko Coin", + "GECKY": "Gecky", + "GEEK": "De:Lithe Last Memories", "GEEQ": "Geeq", + "GEF": "GemFlow", "GEGE": "Gege", "GEIST": "Geist Finance", "GEKKO": "Gekko HQ", @@ -5086,15 +5816,17 @@ "GELO": "Grok Elo", "GEM": "Gemie", "GEMA": "Gemera", - "GEME": "GAMESTUMP", + "GEME": "GEME", "GEMG": "GemGuardian", + "GEMI": "Gemini Inu", "GEMINI": "Gemini Ai", "GEMINIT": "Gemini", - "GEMS": "Gems", + "GEMS": "Gems VIP", "GEMSTON": "GEMSTON", "GEMZ": "Gemz Social", "GEN": "DAOstack", "GENE": "Genopets", + "GENECTO": "Gene", "GENESIS": "Genesis Worlds", "GENI": "Genius", "GENIE": "The Genie", @@ -5105,6 +5837,7 @@ "GENS": "Genshiro", "GENSLR": "Good Gensler", "GENSTAKE": "Genstake", + "GENT": "Gentleman", "GENX": "Genx Token", "GENXNET": "Genesis Network", "GENZ": "GENZ Token", @@ -5124,6 +5857,7 @@ "GET": "Guaranteed Entrance Token", "GETA": "Getaverse", "GETH": "Guarded Ether", + "GETLIT": "LIT", "GETX": "Guaranteed Ethurance Token Extra", "GEX": "Gexan", "GEZY": "EZZY GAME GEZY", @@ -5142,6 +5876,7 @@ "GFY": "go fu*k yourself", "GG": "Reboot", "GGAVAX": "GoGoPool AVAX", + "GGB": "GGEBI", "GGC": "Global Game Coin", "GGCM": "Gold Guaranteed Coin", "GGG": "Good Games Guild", @@ -5149,7 +5884,6 @@ "GGM": "Monster Galaxy", "GGMT": "GG MetaGame", "GGOLD": "GramGold Coin", - "GGP": "GGPro", "GGPT": "Generative GPT", "GGR": "GGRocket", "GGS": "Gilgam", @@ -5158,10 +5892,15 @@ "GGTKN": "GG Token", "GHA": "Ghast", "GHC": "Galaxy Heroes Coin", - "GHCOLD": "Galaxy Heroes Coin", + "GHCV1": "Galaxy Heroes Coin v1", + "GHCV2": "Galaxy Heroes Coin v2", + "GHCV3": "Galaxy Heroes Coin v3", "GHD": "Giftedhands", + "GHDV1": "Giftedhands v1", + "GHE": "GHETTO PEPE", "GHNY": "Grizzly Honey", "GHO": "GHO", + "GHOAD": "GhoadCoin", "GHOST": "GhostbyMcAfee", "GHOSTCOIN": "GhostCoin", "GHOSTM": "GhostMarket", @@ -5179,14 +5918,21 @@ "GIF": "Gift Token", "GIFT": "GiftNet", "GIG": "GigaCoin", - "GIGA": "GigaSwap", + "GIGA": "Gigachad", + "GIGACAT": "GIGACAT", "GIGACHAD": "GigaChad", + "GIGASWAP": "GigaSwap", + "GIGGLE": "Giggle Academy", + "GIGS": "Climate101", "GIGX": "GigXCoin", "GIKO": "Giko Cat", + "GILOEX": "Gilo", "GIM": "Gimli", "GIMMER": "Gimmer", + "GIMMERV1": "Gimmer v1", "GIN": "GINcoin", "GINGER": "GINGER", + "GINNAN": "Ginnan The Cat", "GINOA": "Ginoa", "GINUX": "Green Shiba Inu", "GINZA": "GINZA NETWORK", @@ -5194,15 +5940,20 @@ "GIOT": "Giotto Coin", "GIOVE": "GIOVE", "GIR": "Girlfriend", + "GIRLS": "Girls Club", + "GITH": "GitHub's Mascot Octocat", "GIV": "Giveth", "GIVE": "GiveCoin", "GIZ": "GIZMOcoin", + "GIZMO": "GIZMO•IMAGINARY• KITTEN (Runes)", "GJC": "Global Jobcoin", + "GKAPPA": "Golden Kappa", "GKF": "Galatic Kitty Fighters", "GKI": "GKi", "GL": "Lemmings", "GLA": "Gladius", "GLAX": "BLOCK GALAXY NETWORK", + "GLAZE": "Glaze", "GLB": "Golden Ball", "GLC": "GoldCoin", "GLCH": "Glitch", @@ -5218,13 +5969,17 @@ "GLFT": "Global Fan Token", "GLI": "GLI TOKEN", "GLIDE": "Glide Finance", + "GLIESE": "GlieseCoin", "GLINK": "Gemlink", "GLINT": "BeamSwap", "GLM": "Golem Network Token", "GLMR": "Moonbeam", + "GLMV1": "Golem Network Token v1", "GLN": "Galion Token", + "GLO": "Global Innovation Platform", "GLOBAL": "GlobalCoin", "GLOBE": "Global", + "GLORP": "Glorp", "GLORY": "SEKAI GLORY", "GLOS": "GLOS", "GLOWSHA": "GlowShares", @@ -5247,6 +6002,7 @@ "GME": "GameStop", "GMEE": "GAMEE", "GMEPEPE": "GAMESTOP PEPE", + "GMETHERFRENS": "GM", "GMETRUMP": "GME TRUMP", "GMEX": "Game Coin", "GMFAM": "GMFAM", @@ -5254,11 +6010,13 @@ "GMI": "GamiFi", "GML": "GameLeagueCoin", "GMM": "Gamium", - "GMMT": "Green Mining Movement Token", + "GMMT": "Giant Mammoth", "GMNG": "Global Gaming", "GMNT": "Gmining", "GMPD": "GamesPad", "GMR": "GAMER", + "GMRV1": "GAMER v1", + "GMRV2": "GAMER v2", "GMRX": "Gaimin", "GMS": "Gemstra", "GMT": "STEPN", @@ -5276,6 +6034,8 @@ "GNNX": "Gennix", "GNO": "Gnosis", "GNOME": "GNOME", + "GNOMY": "Gnomy", + "GNON": "Numogram", "GNR": "Gainer", "GNS": "Gains Network", "GNT": "GreenTrust", @@ -5288,11 +6048,17 @@ "GOAL": "GOAL token", "GOALBON": "Goal Bonanza", "GOALS": "UnitedFans", - "GOAT": "Goat", + "GOAT": "Goatseus Maximus", + "GOATAI": "GOAT AI", + "GOATCOIN": "Goat", + "GOATSE": "GOATSE", "GOB": "Goons of Balatroon", "GOC": "GoCrypto", + "GOCHU": "Gochujangcoin", "GOD": "Bitcoin God", + "GODCAT": "GodcatExplodingKittens", "GODE": "Gode Chain", + "GODEX": "GUARD OF DECENT", "GODL": "GODL", "GODS": "Gods Unchained", "GODZ": "Cryptogodz", @@ -5301,6 +6067,7 @@ "GOFF": "Gift Off Token", "GOFX": "GooseFX", "GOG": "Guild of Guardians", + "GOGLZ": "GOGGLES", "GOGO": "GOGO Finance", "GOGU": "GOGU Coin", "GOIN": "GOinfluencer", @@ -5310,43 +6077,63 @@ "GOLC": "GOLCOIN", "GOLD": "CyberDragon Gold", "GOLDCAT": "GOLD CAT", + "GOLDCOINETH": "Gold", + "GOLDE": "GOLDEN AGE", "GOLDEN": "Golden Inu", "GOLDENG": "Golden Goose", "GOLDF": "Gold Fever", "GOLDMIN": "GoldMiner", + "GOLDN": "GoLondon", "GOLDPIECES": "GoldPieces", + "GOLDS": "Gold Standard", "GOLDX": "eToro Gold", "GOLDY": "DeFi Land Gold", "GOLF": "GolfCoin", + "GOLFI": "Golf is Boring", "GOLOS": "Golos", "GOLOSBLOCKCHAIN": "Golos Blockchain", "GOM": "Gomics", "GOM2": "GoMoney2", "GOMA": "GOMA Finance", + "GOMAV1": "GOMA Finance v1", + "GOMAV2": "GOMA Finance v2", "GOMD": "GOMDori", "GOME": "Game of Memes", "GOMT": "GoMeat", + "GOMV1": "GoMoney", + "GONDOLA": "Gondola", "GONE": "GONE", + "GONG": "GONG", "GOO": "Gooeys", "GOOCH": "Gooch", "GOOD": "Goodomy", + "GOODM": "Good Morning!", + "GOODMO": "Good Morning", + "GOOG": "Googly Cat", "GOOGLE": "Deepmind Ai", "GOOGLY": "Googly Cat", + "GOOMPY": "Goompy by Matt Furie", "GOON": "Goonies", + "GOP": "The Republican Party", "GOPX": "GOPX Token", "GORA": "Gora", "GOREC": "GoRecruit", "GORGONZOLA": "Heroes 3 Foundation", + "GORGONZOLAV1": "Heroes 3 Foundation v1", "GORILLA": "Gorilla", "GORILLAD": "Gorilla Diamond", "GORILLAINU": "Gorilla Inu", + "GORPLE": "GorplesCoin", "GOS": "Gosama", "GOSS": "GOSSIP-Coin", "GOST": "SoulCoin", "GOT": "ParkinGo", "GOTEM": "gotEM", "GOTG": "Got Guaranteed", + "GOTTI": "Gotti Token", "GOTX": "GothicCoin", + "GOU": "Gou", + "GOUT": "GOUT", "GOV": "SubDAO", "GOVI": "Govi", "GOVT": "The Government Network", @@ -5379,8 +6166,11 @@ "GRAI": "Gravita Protocol", "GRAIL": "Camelot Token", "GRAIN": "Granary", + "GRAM": "Gram", "GRANDCOIN": "GrandCoin", + "GRANDMA": "Grandma", "GRAPE": "GrapeCoin", + "GRASS": "Grass", "GRAV": "Graviton", "GRAVITAS": "Gravitas", "GRAVITYF": "Gravity Finance", @@ -5390,10 +6180,15 @@ "GRC": "GreenCoin.AI", "GRE": "GreenCoin", "GREARN": "GrEarn", + "GREE": "Green God Candle", "GREEN": "GreenX", + "GREENH": "Greenheart CBD", + "GREENMMT": "Green Mining Movement Token", + "GREENPOWER": "GreenPower", "GREENT": "Greentoken", "GREG": "greg", "GRELF": "GRELF", + "GREMLY": "Gremly", "GREXIT": "GrexitCoin", "GREY": "Grey Token", "GRFT": "Graft Blockchain", @@ -5410,9 +6205,11 @@ "GRLC": "Garlicoin", "GRM": "GridMaster", "GRMD": "GreenMed", - "GRN": "GreenPower", + "GRN": "GRN Grid", "GRND": "SuperWalk", + "GRNV1": "GRN Grid v1", "GRO": "Gro DAO Token", + "GROGGO": "Groggo By Matt Furie", "GROK": "Grok", "GROK2": "GROK 2.0", "GROKBANK": "Grok Bank", @@ -5480,6 +6277,7 @@ "GTAVI": "GTAVI", "GTBOT": "Gaming-T-Bot", "GTC": "Gitcoin", + "GTCC": "GTC COIN", "GTCOIN": "Game Tree", "GTE": "GreenTek", "GTF": "GLOBALTRUSTFUND TOKEN", @@ -5494,6 +6292,7 @@ "GTSE": "Global Tourism Sharing Ecology", "GTTM": "Going To The Moon", "GTX": "GALLACTIC", + "GUA": "GUA", "GUAC": "Guacamole", "GUAP": "Guapcoin", "GUAR": "Guarium", @@ -5503,6 +6302,7 @@ "GUCCI": "GUCCI", "GUE": "GuerillaCoin", "GUESS": "Peerguess", + "GUGU": "gugu", "GUI": "Gui Inu", "GUILD": "BlockchainSpace", "GUISE": "GUISE", @@ -5515,14 +6315,17 @@ "GUNS": "GeoFunders", "GUP": "Guppy", "GURL": "Gently Used Girl", + "GURU": "Guru Network", "GUSD": "Gemini Dollar", "GUSDT": "Global Utility Smart Digital Token", "GUT": "Genesis Universe", "GUUFY": "Guufy", + "GUZUTA": "CLYDE", "GVC": "Global Virtual Coin", "GVE": "Globalvillage Ecosystem", "GVL": "Greever", "GVR": "Grove [OLD]", + "GVRV1": "Grove v1", "GVT": "Genesis Vision", "GW": "Gyrowin", "GWD": "GreenWorld", @@ -5556,8 +6359,13 @@ "HABIBI": "The Habibiz", "HAC": "Hackspace Capital", "HACD": "Hacash Diamond", + "HACH": "Hachiko", "HACHI": "Hachi", + "HACHIK": "Hachiko", "HACHIKO": "Hachiko Inu Token", + "HACHIONB": "Hachi On Base", + "HACK": "HACK", + "HAGGIS": "New Born Haggis Pygmy Hippo", "HAHA": "Hasaki", "HAI": "Hacken Token", "HAIR": " HairDAO", @@ -5575,9 +6383,12 @@ "HAMI": "Hamachi Finance", "HAMMY": "SAD HAMSTER", "HAMS": "HamsterCoin", + "HAMSTER": "Space Hamster", + "HAMSTERB": "HamsterBase", "HAMSTR": "Hamster Coin", "HAN": "HanChain", "HANA": "Hanacoin", + "HANAETHCTO": "HANA", "HAND": "ShowHand", "HANDY": "Handy", "HANK": "Hank", @@ -5585,12 +6396,18 @@ "HAO": "HistoryDAO", "HAP": "Happy Train", "HAPI": "HAPI", + "HAPPY": "Happy Cat", + "HAR": "Harambe Coin", "HARAM": "HARAM", "HARAMBE": "Harambe on Solana", "HARD": "Kava Lend", "HARE": "Hare Token", "HAREPLUS": "Hare Plus", "HAROLD": "Harold", + "HARPER": "Harper", + "HARR": "HARRIS DOGS", + "HARRIS": "KAMALA HARRIS", + "HARRISV": "Harris V Trump", "HARRYP": "HarryPotterObamaSonic10Inu (ERC20)", "HART": "HARA", "HASBIK": "Hasbulla", @@ -5605,17 +6422,22 @@ "HAUS": "DAOhaus", "HAVOC": "Havoc", "HAVY": "Havy", + "HAW": "Hawk Tuah", "HAWK": "Hawksight", + "HAWKCITY": "Hawk", + "HAWKPTAH": "Hawk Ptah", "HAWKTUAH": "Hawk Tuah", - "HAY": "Destablecoin HAY", + "HAXS": "Axie Infinity Shards (Harmony One Bridge)", "HAYYA": "GO HAYYA", "HAZ": "Hazza", "HAZE": "HazeCoin", "HB": "HeartBout", "HBAR": "Hedera Hashgraph", + "HBARBARIAN": "HBARbarian", "HBARX": "HBARX", "HBB": "Hubble", "HBC": "HBTC Captain Token", + "HBCH": "Huobi BCH", "HBD": "Hive Dollar", "HBDC": "Happy Birthday Coin", "HBE": "healthbank", @@ -5624,9 +6446,11 @@ "HBO": "Hash Bridge Oracle", "HBOT": "Hummingbot", "HBRS": "HubrisOne", + "HBSV": "Huobi BSV", "HBT": "Habitat", "HBTC": "Huobi BTC", "HBX": "Hyperbridge", + "HBZ": "HBZ Coin", "HC": "HyperCash", "HCC": "HappyCreatorCoin", "HCT": "HurricaneSwap Token", @@ -5641,7 +6465,9 @@ "HDV": "Hydraverse", "HDX": "HydraDX", "HE": "Heroes & Empires", + "HEA": "Healium", "HEAL": "Etheal", + "HEALT": "Healthmedi", "HEART": "Humans", "HEARTBOUT": "HeartBout Pay", "HEARTR": "Heart Rate", @@ -5657,11 +6483,17 @@ "HEGE": "Hege", "HEGG": "Hummingbird Egg", "HEGIC": "Hegic", + "HEHE": "hehe", + "HEL": "Hello Puppy", + "HELA": "Science Cult Mascot", + "HELI": "Helion", + "HELINK": "Chainlink (Huobi Exchange)", "HELIOS": "Mission Helios", "HELL": "HELL COIN", "HELLO": "HELLO", "HELMET": "Helmet Insure", "HELPS": "HelpSeed", + "HEM": "Hemera", "HEMAN": "HE-MAN", "HEMULE": "Hemule", "HEP": "Health Potion", @@ -5669,13 +6501,17 @@ "HERA": "Hero Arena", "HERB": "HerbCoin", "HERBE": "Herbee", + "HERME": "Hermes DAO", "HERMES": "Hermes Protocol", "HERO": "Metahero", + "HEROC": "HEROcoin", "HEROES": "Dehero Community Token", "HEROESC": "HeroesChained", + "HEROI": "Heroic Saga Shiba", "HET": "HavEther", "HETA": "HetaChain", "HETH": "Huobi Ethereum", + "HEWE": "Health & Wealth", "HEX": "HEX", "HEXC": "HexCoin", "HEZ": "Hermez Network Token", @@ -5683,6 +6519,7 @@ "HFI": "Holder Finance", "HFIL": "Huobi Fil", "HFT": "Hashflow", + "HFUN": "Hold.fun", "HGEN": "HGEN DAO", "HGET": "Hedget", "HGHG": "HUGHUG Coin", @@ -5718,6 +6555,7 @@ "HIH": "HiHealth", "HIKARI": "Hikari Protocol", "HILL": "President Clinton", + "HILO": "HILO", "HIM": "Human Intelligence Machine", "HIMAYC": "hiMAYC", "HIME": "Phantom of the Kill", @@ -5733,12 +6571,14 @@ "HIP": "HIPPOP", "HIPENGUINS": "hiPENGUINS", "HIPP": "El Hippo", + "HIPPO": "sudeng", "HIPUNKS": "hiPUNKS", "HIRE": "HireMatch", "HIRENGA": "hiRENGA", "HISAND33": "hiSAND33", "HISEALS": "hiSEALS", "HISQUIGGLE": "hiSQUIGGLE", + "HISS": "Snake of Solana", "HIT": "HitChain", "HITBTC": "HitBTC Token", "HITOP": "Hitop", @@ -5757,6 +6597,7 @@ "HLD": "HyperLending", "HLDY": "HOLIDAY", "HLG": "Holograph", + "HLINK": "Chainlink (Harmony One Bridge)", "HLM": "Helium", "HLN": "Holonus", "HLP": "Purpose Coin", @@ -5764,6 +6605,7 @@ "HLPT": "HLP Token", "HLS": "Halis", "HLT": "HyperLoot", + "HLTC": "Huobi LTC", "HLX": "Helex", "HMC": "Hi Mutual Society", "HMD": "Homelend", @@ -5777,12 +6619,15 @@ "HMR": "Homeros", "HMRN": "Homerun", "HMST": "Hamster Marketplace Token", + "HMSTR": "Hamster Kombat", "HMT": "HUMAN Token", "HMTT": "Hype Meme Token", + "HMU": "hit meeee upp", "HMX": "HMX", "HNB": "HashNet BitEco", "HNC": "Hellenic Coin", "HNCN": "Huncoin", + "HND": "Hundred Finance", "HNS": "Handshake", "HNST": "Honest", "HNT": "Helium", @@ -5812,21 +6657,29 @@ "HOM": "Homeety", "HOME": "OtterHome", "HOMER": "Homer Simpson", + "HOMERB": "Homer BSC", + "HOMERO": "Homer Of Meme", "HOMI": "HOMIHELP", "HOMIECOIN": "Homie Wars", "HOMMIES": "HOMMIES", + "HOMS": "Heroes of memes", "HON": "SoulSociety", "HONEY": "Hivemapper", "HONEYCOIN": "Honey", "HONG": "HongKongDAO", "HONK": "Honk", + "HONKLER": "Honkler", "HONOR": "HonorLand", "HOOF": "Metaderby Hoof", "HOOK": "Hooked Protocol", "HOOP": "Chibi Dinos", + "HOOPS": "Hoops", + "HOOT": "HOOT", "HOP": "Hop Protocol", "HOPPY": "Hoppy", + "HOPPYTOKEN": "Hoppy", "HOPR": "HOPR", + "HOR": "HorizonDEX", "HORD": "Hord", "HORSE": "Ethorse", "HORUS": "HorusPay", @@ -5865,6 +6718,7 @@ "HRDG": "HRDGCOIN", "HRM": "Honorarium", "HRO": "HEROIC.com", + "HRSE": "The Winners Circle", "HRT": "HIRO", "HRTS": "YellowHeart Protocol", "HRX": "HorusLayer", @@ -5877,6 +6731,7 @@ "HST": "Decision Token", "HSUI": "Suicune", "HSUITE": "HbarSuite", + "HSUSDC": "Holdstation USDC", "HT": "Huobi Token", "HTA": "Historia", "HTB": "Hotbit", @@ -5885,6 +6740,7 @@ "HTDF": "Orient Walt", "HTE": "Hepton", "HTER": "Biogen", + "HTK": "Hard To Kill", "HTM": "Hatom", "HTML": "HTML Coin", "HTMOON": "HTMOON", @@ -5897,9 +6753,11 @@ "HUAHUA": "Chihuahua Chain", "HUB": "Hub Token", "HUBII": "Hubii Network", + "HUBSOL": "SolanaHub staked SOL", "HUC": "HunterCoin", "HUDI": "Hudi", - "HUGE": "BigCoin", + "HUE": "Huebel Bolt", + "HUGE": "HugeWin", "HUGO": "Hugo Inu", "HUH": "HUH Token", "HUHCAT": "huhcat", @@ -5912,6 +6770,7 @@ "HUNT": "HUNT", "HUR": "Hurify", "HUS": "HUSSY", + "HUSBY": "HUSBY", "HUSD": "HUSD", "HUSH": "Hush", "HUSKY": "Husky", @@ -5940,6 +6799,7 @@ "HYBRID": "Hybrid Bank Cash", "HYC": "HYCON", "HYCO": "HYPERCOMIC", + "HYD": "HYDRA", "HYDRA": "Hydra", "HYDRO": "Hydro", "HYDROMINER": "Hydrominer", @@ -5965,6 +6825,7 @@ "HZN": "Horizon Protocol", "HZT": "HazMatCoin", "I0C": "I0coin", + "I3D": "i3D Protocol", "I7": "ImpulseVen", "I9C": "i9 Coin", "IAG": "IAGON", @@ -5989,8 +6850,11 @@ "ICAP": "ICAP Token", "ICASH": "ICASH", "ICB": "IceBergCoin", + "ICBX": "ICB Network", "ICC": "Insta Cash Coin", "ICE": "Ice Open Network", + "ICEC": "IceCream", + "ICECR": "Ice Cream Sandwich", "ICELAND": "ICE LAND", "ICETH": "Interest Compounding ETH Index", "ICG": "Invest Club Global", @@ -5998,6 +6862,7 @@ "ICHI": "ICHI", "ICHN": "i-chain", "ICHX": "IceChain", + "ICL": "ICLighthouse DAO", "ICLICK": "Iclick inu", "ICN": "Iconomi", "ICNX": "Icon.X World", @@ -6046,9 +6911,11 @@ "IEC": "IvugeoEvolutionCoin", "IETH": "iEthereum", "IF": "Impossible Finance", + "IFBTC": "Ignition FBTC", "IFC": "Infinite Coin", "IFIT": "CALO INDOOR", "IFLT": "InflationCoin", + "IFOR": "iFortune", "IFT": "InvestFeed", "IFUM": "Infleum", "IFUND": "Unifund", @@ -6058,6 +6925,7 @@ "IGG": "IG Gold", "IGI": "Igi", "IGNIS": "Ignis", + "IGT": "Infinitar", "IGTT": "IGT", "IGU": "IguVerse", "IGUP": "IguVerse", @@ -6067,7 +6935,9 @@ "IIC": "Intelligent Investment Chain", "IJC": "IjasCoin", "IJZ": "iinjaz", + "IJZV1": "iinjaz v1", "IKI": "ikipay", + "IKIGAI": "Ikigai", "ILA": "Infinite Launch", "ILC": "ILCOIN", "ILCT": "ILCoin Token", @@ -6103,10 +6973,12 @@ "IMVR": "ImmVRse", "IMX": "Immutable X", "IN": "InCoin", + "INA": "pepeinatux", "INARI": "Inari", "INB": "Insight Chain", "INC": "Incrementum", "INCAKE": "InfinityCAKE", + "INCEPT": "Incept", "INCNT": "Incent", "INCORGNITO": "Incorgnito", "INCP": "InceptionCoin", @@ -6116,6 +6988,7 @@ "INDEX": "Index Cooperative", "INDI": "IndiGG", "INDIA": "Indiacoin", + "INDIAN": "Indian Call Center", "INDICOIN": "IndiCoin", "INDU": "INDU4.0", "INDY": "Indigo Protocol", @@ -6124,10 +6997,11 @@ "INERY": "Inery", "INES": "Inescoin", "INET": "Insure Network", + "INETH": "Inception Restaked ETH", "INEX": "Inex Project", "INF": "Infinium", "INFC": "Influence Chain", - "INFI": "Insured Finance", + "INFI": "Infinite", "INFINI": "Infinity Economics", "INFLR": "Inflr", "INFO": "Infomatix", @@ -6145,18 +7019,24 @@ "INOVAI": "INOVAI", "INP": "Ionic Pocket Token", "INRT": "INRToken", + "INRX": "INRx", + "INRXV1": "INRx v1", "INS": "Insolar (Old Chain)", "INSANE": "InsaneCoin", "INSANITY": "Insanity Coin", "INSC": "INSC (Ordinals)", + "INSE": "INSECT", "INSN": "Insane Coin", "INSP": "Inspect", + "INSPI": "InspireAI", "INSR": "Insurabler", "INST": "Instadapp", "INSTAMINE": "Instamine Nuggets", "INSTAR": "Insights Network", "INSUR": "InsurAce", + "INSURANCE": "insurance", "INSURC": "InsurChain Coin", + "INSUREDFIN": "Insured Finance", "INT": "Internet Node token", "INTD": "INTDESTCOIN", "INTE": "InteractWith", @@ -6179,6 +7059,7 @@ "INVESTEL": "Investelly token", "INVI": "INVI Token", "INVIC": "Invictus", + "INVITE": "INVITE Token", "INVOX": "Invox Finance", "INVX": "Investx", "INX": "Insight Protocol", @@ -6215,6 +7096,7 @@ "IPOR": "IPOR", "IPSX": "IP Exchange", "IPT": "Crypt-ON", + "IPU": "iPulse", "IPUX": "IPUX", "IPV": "IPVERSE", "IPVOLD": "IPVERSE (Klaytn)", @@ -6232,12 +7114,14 @@ "IRIS": "IRIS Network", "IRISTOKEN": "Iris Ecosystem", "IRL": "IrishCoin", + "IRO": "Iro-Chan", "IRON": "Iron Fish", "IRONBSC": "Iron BSC", "IRT": "Infinity Rocket", "IRYDE": "iRYDE COIN", "ISA": "Islander", "ISDT": "ISTARDUST", + "ISEC": "IntelliSecure Systems", "ISG": "ISG", "ISH": "Interstellar Holdings", "ISHI": "Ishi", @@ -6268,6 +7152,7 @@ "ITL": "Italian Lira", "ITLR": "MiTellor", "ITM": "intimate.io", + "ITO": "Ito-chan", "ITOC": "ITOChain", "ITR": "INTRO", "ITSB": "ITSBLOC", @@ -6282,6 +7167,7 @@ "IVAR": "Ivar Coin", "IVC": "Investy Coin", "IVEX": "IVEX Financial", + "IVFUN": "Invest Zone", "IVI": "IVIRSE", "IVIP": "iVipCoin", "IVN": "IVN Security", @@ -6293,6 +7179,7 @@ "IWT": "IwToken", "IX": "X-Block", "IXC": "IXcoin", + "IXIR": "IXIR", "IXP": "IMPACTXPRIME", "IXS": "IX Swap", "IXT": "iXledger", @@ -6313,17 +7200,23 @@ "JACY": "JACY", "JADE": "Jade Protocol", "JADEC": "Jade Currency", + "JAGO": "Jagotrack", "JAIHO": "Jaiho Crypto", "JAKE": "Jake The Dog", "JAM": "Tune.Fm", "JAN": "Storm Warfare", "JANE": "JaneCoin", + "JANET": "Janet", + "JANI": "JANI", "JAR": "Jarvis+", "JARED": "Jared From Subway", "JARY": "JeromeAndGary", "JASMY": "JasmyCoin", + "JASON": "Jason Derulo", + "JAV": "Javsphere", "JAWS": "AutoShark", "JAY": "Jaypeggers", + "JBO": "JBOX", "JBOT": "JACKBOT", "JBS": "JumBucks Coin", "JBX": "Juicebox", @@ -6334,13 +7227,16 @@ "JCO": "JennyCo", "JCR": "JustCarbon Removal", "JCT": "Japan Content Token", + "JDAI": "Dai (TON Bridge)", "JDC": "JustDatingSite", + "JDO": "JINDO", "JED": "JEDSTAR", "JEDALS": "Yoda Coin Swap", "JEET": "Jeet", "JEETOLAX": "Jeetolax", "JEFE": "JEFE TOKEN", "JEFF": "Jeff in Space", + "JEFFRY": "jeffry", "JEJUDOGE": "Jejudoge", "JELLI": "JELLI", "JELLY": "Jelly eSports", @@ -6351,8 +7247,10 @@ "JERRY": "Jerry Inu", "JERRYINU": "JERRYINU", "JES": "Jesus", + "JEST": "Jester", "JESUS": "Jesus Coin", "JET": "Jet Protocol", + "JETCAT": "Jetcat", "JETCOIN": "Jetcoin", "JETTON": "JetTon Game", "JEUR": "Jarvis Synthetic Euro", @@ -6365,11 +7263,13 @@ "JFIVE": "Jonny Five", "JGLP": "Jones GLP", "JGN": "Juggernaut", + "JHH": "Jen-Hsun Huang", "JIAOZI": "Jiaozi", "JIB": "Jibbit", "JIF": "JiffyCoin", "JIG": "Jigen", "JIM": "Jim", + "JIN": "JinPeng", "JIND": "JINDO INU", "JINDOGE": "Jindoge", "JIO": "JIO Token", @@ -6382,6 +7282,7 @@ "JKC": "JunkCoin", "JKL": "Jackal Protocol", "JLP": "Jupiter Perps LP", + "JLY": "Jellyverse", "JM": "JustMoney", "JMC": "Junson Ming Chan Coin", "JMPT": "JumpToken", @@ -6397,6 +7298,7 @@ "JOBS": "JobsCoin", "JOC": "Speed Star JOC", "JOE": "JOE", + "JOEB": "Joe Biden", "JOEBIDEN2024 ": "JOEBIDEN2024", "JOEY": "Joey Inu", "JOGECO": "Jogecodog", @@ -6407,13 +7309,20 @@ "JOK": "JokInTheBox", "JOKER": "JOKER", "JOKERCOIN": "JokerCoin", + "JOKERERC": "Joker", "JOL": "Jolofcoin", "JOLT": "Joltify", + "JOMA": "Joma", "JONES": "Jones DAO", + "JONESUSDC": "Jones USDC", "JOOPS": "JOOPS", + "JOPER": "Joker Pepe", + "JOSE": "Jose", "JOTCHUA": "Perro Dinero", + "JOULE": "Joule", "JOWNES": "Alux Jownes", "JOY": "Joystream", + "JOYCAT": "JoyCat Coin", "JOYS": "JOYS", "JOYT": "JoyToken", "JOYTOKEN": "Joycoin", @@ -6427,6 +7336,7 @@ "JRIT": "JERITEX", "JRT": "Jarvis Reward Token", "JSE": "JSEcoin", + "JSET": "Jsetcoin", "JSM": "Joseon Mun", "JSOL": "JPool Staked SOL", "JST": "JUST", @@ -6439,44 +7349,61 @@ "JUDGE": "JudgeCoin", "JUGNI": "JUGNI", "JUI": "Juiice", + "JUIC": "Juice", "JUICE": "Juice Finance", + "JUICEB": "Juice", "JUL": "Joule", "JULB": "JustLiquidity Binance", "JULD": "JulSwap", + "JUM": "Jumoney", "JUMBO": "Jumbo Exchange", "JUMP": "Jumpcoin", "JUN": "Jun \"M\" Coin", "JUNGLE": "JUNGLEDOGE", + "JUNGLEKING": "JungleKing TigerCoin", + "JUNIOR": "Junior", "JUNKIE": "Junkie Cats", "JUNO": "JUNO", "JUP": "Jupiter", "JUPI": "Jupiter", "JUPSOL": "Jupiter Staked SOL", "JUR": "Jur", + "JUS": "Just The Tip", "JUSD": "JUSD Stable Token", - "JUSDC": "Jones USDC", + "JUSDC": "USD Coin (TON Bridge)", "JUSDT": "TON Bridged USDT", + "JUST": "just a cat", + "JUSTI": "Justin MEME", "JUSTICE": "AssangeDAO", "JUV": "Juventus Fan Token", "JVL": "Javelin", + "JVT": "JVault", "JVY": "Javvy", "JW": "Jasan Wellness", + "JWBTC": "Wrapped Bitcoin (TON Bridge)", "JWIF": "Jerrywifhat", "JWL": "Jewels", "JYC": "Joe-Yo Coin", "K21": "K21", "K2G": "Kasko2go", + "KAAI": "KanzzAI", "KAAS": "KAASY.AI", + "KAB": "KABOSU", "KABOSU": "Kabosu Family", "KABY": "Kaby Arena", "KAC": "KACO Finance", "KACY": "Kassandra", + "KADYROV": "Ramzan", "KAF": "KAIF Platform", "KAG": "Silver", + "KAGE": "Kage Network", "KAI": "KardiaChain", + "KAIA": "Kaia", "KAID": "KAIDEX", "KAIJU": "KAIJUNO8", + "KAIK": "KAI KEN", "KAIKEN": "Kaiken Shiba", + "KAILY": "Kailith", "KAINET": "KAINET", "KAKA": "KAKA NFT World", "KAKAXA": "KAKAXA", @@ -6491,13 +7418,22 @@ "KALM": "KALM", "KALYCOIN": "KalyCoin", "KAM": "BitKAM", + "KAMA": "Kamala Horris", + "KAMAL": "Kamala Harris", + "KAMALA": "Kamala Harris", + "KAMALAHARRIS": "KAMALA HARRIS", + "KAMLA": "KAMALAMA (kamalama.org)", "KAMPAY": "KamPay", "KAN": "Bitkan", + "KANG": "Kangamoon", "KANG3N": "Kang3n", "KANGAL": "Kangal", + "KANGO": "KANGO", "KAP": "KAP Games", "KAPU": "Kapu", "KAR": "Karura", + "KARA": "KarateCat", + "KARAT": "KARAT Galaxy", "KARATE": "Karate Combat", "KAREN": "KarenCoin", "KARMA": "Karma", @@ -6505,6 +7441,10 @@ "KARRAT": "KARRAT", "KART": "Dragon Kart", "KAS": "Kaspa", + "KASBOT": "KASBOT THE GUARDIAN OF 𐤊ASPA", + "KASHIN": "KASHIN", + "KASPER": "Kasper the ghost of Kaspa", + "KASPY": "KASPY", "KASSIAHOME": "Kassia Home", "KASTA": "Kasta", "KAT": "Kambria", @@ -6533,6 +7473,7 @@ "KCAL": "Phantasma Energy", "KCASH": "Kcash", "KCAT": "KING OF CATS", + "KCATS": "KASPA CATS", "KCCM": "KCC MemePad", "KCCPAD": "KCCPad", "KCH": "Keep Calm and Hodl", @@ -6560,15 +7501,20 @@ "KELP": "KELP", "KELPE": "Kelp Earned Points", "KELPIE": "Kelpie Inu", + "KEM": "Kem Jeng Un", "KEMA": "Kemacoin", - "KEN": "Kencoin", + "KEN": "Ken", + "KENCOIN": "Kencoin", "KENDU": "Kendu Inu", "KENKA": "KENKA METAVERSE", "KENNEL": "Kennel Locker", + "KENOBI": "Obi PNut Kenobi", "KENSHI": "Kenshi", "KEP": "Kepler", "KEPT": "KeptChain", "KERMIT": "KermitTheCoin", + "KERN": "Kernel", + "KET": "KET", "KETAMINE": "Ketamine", "KETAN": "Ketan", "KEX": "Kira Network", @@ -6577,6 +7523,7 @@ "KEYC": "KeyCoin", "KEYCAT": "Keyboard Cat", "KEYFI": "KeyFi", + "KEYS": "KEYS", "KEYT": "REBIT", "KFC": "Chicken", "KFI": "Klever Finance", @@ -6588,6 +7535,7 @@ "KGO": "Kiwigo", "KGT": "Kaby Gaming Token", "KHAI": "khai", + "KHEOWZOO": "khaokheowzoo", "KHM": "Kohima", "KI": "Genopets KI", "KIAN": "Porta", @@ -6595,6 +7543,7 @@ "KIBSHI": "KiboShib", "KICK": "Kick", "KICKS": "GetKicks", + "KIDEN": "RoboKiden", "KIF": "KittenFinance", "KIKO": "KIKO", "KILLA": "The Bitcoin Killa", @@ -6607,15 +7556,20 @@ "KIN": "Kin", "KIND": "Kind Ads", "KINE": "Kine Protocol", + "KINET": "KinetixFi", "KING": "KING", "KING93": "King93", "KINGB": "King Bean", "KINGBONK": "King Bonk", "KINGCAT": "King Cat", + "KINGD": "Kingdom of Ants", "KINGDOG": "King Dog Inu", "KINGDOMQUEST": "Kingdom Quest", "KINGF": "King Finance", "KINGGROK": "King Grok", + "KINGNEIRO": "King Neiro", + "KINGO": "King of memes", + "KINGOF": "King Of Memes", "KINGPEPE": "KING PEPE", "KINGSHIB": "King Shiba", "KINGSLERF": "King Slerf", @@ -6626,6 +7580,7 @@ "KINGWIF": "King WIF", "KINGY": "KINGYTON", "KINIC": "Kinic", + "KINK": "Kinka", "KINT": "Kintsugi", "KINU": "Kragger Inu", "KIRA": "Kira the Injective Cat", @@ -6639,17 +7594,24 @@ "KISHU": "Kishu Inu", "KIT": "Kitsune", "KITA": "KITA INU", + "KITE": "Kite", + "KITEAI": "KITEAI", "KITSU": "Kitsune Inu", + "KITTE": "Kittekoin", "KITTENS": "Kitten Coin", "KITTENWIF": "KittenWifHat", "KITTI": "KITTI TOKEN", - "KITTY": "Kitty Inu", + "KITTY": "Roaring Kitt", + "KITTYINU": "Kitty Inu", + "KITTYINUV1": "Kitty Inu v1", + "KITTYS": "KITTY Sol", + "KITTYSOL": "Kitty Solana", "KIWI": "kiwi", "KIZUNA": "KIZUNA", "KKO": "Kineko", "KKT": "Kingdom Karnage", "KLAP": "Klap Finance", - "KLAY": "Klaytn", + "KLAUS": "Klaus", "KLC": "KiloCoin", "KLD": "Koduck", "KLEE": "KleeKai", @@ -6664,6 +7626,7 @@ "KLT": "Kamaleont", "KLUB": "KlubCoin", "KLV": "Klever", + "KLY": "Klayr", "KMA": "Calamari Network", "KMC": "Kitsumon", "KMD": "Komodo", @@ -6680,6 +7643,7 @@ "KNFT": "KStarNFT", "KNG": "BetKings", "KNGN": "KingN Coin", + "KNI": "Knights of Cathena", "KNIGHT": "Forest Knight", "KNINE": "K9 Finance", "KNJ": "Kunji Finance", @@ -6690,7 +7654,9 @@ "KNS": "Kenshi", "KNT": "Knekted", "KNTO": "Kento", + "KNU": "Keanu", "KNW": "Knowledge", + "KOAI": "KOI", "KOBE": "Shabu Shabu", "KOBO": "KoboCoin", "KODA": "Koda Cryptocurrency", @@ -6706,19 +7672,26 @@ "KOK": "KOK Coin", "KOKO": "KokoSwap", "KOL": "Kollect", + "KOLANA": "KOLANA", "KOLION": "Kolion", + "KOLT": "Kolt", "KOM": "Kommunitas", + "KOMA": "Koma Inu", "KOMO": "Komoverse", "KOMP": "Kompass", "KOMPETE": "KOMPETE", "KON": "KonPay", + "KONAN": "Konan of Kaspa", + "KONET": "KONET", "KONG": "KONG", "KONO": "Konomi Network", "KORA": "Kortana", + "KORC": "King of Referral Coin", "KORE": "KORE Vault", "KOREC": "Kore", "KORRA": "KORRA", "KOSS": "Koss", + "KOTARO": "KOTARO", "KOTO": "Koto", "KOY": "Koyo", "KOZ": "Kozjin", @@ -6726,13 +7699,19 @@ "KP4R": "Keep4r", "KPAD": "KickPad", "KPAPA": "KPAPA", + "KPAW": "KasPaw", "KPC": "KEEPs Coin", "KPHI": "Kephi Gallery", + "KPK": "ParkCoin", "KPL": "Kepple", "KPN": "KonnektVPN", "KPOP": "KPOP Coin", + "KPOPFUN": "KPOP (kpop.fun)", "KRAK": "Kraken", "KRATOS": "KRATOS", + "KRAV": "Krav", + "KRAZY": "krazy n.d.", + "KRAZYKAMALA": "KRAZY KAMALA", "KRB": "Karbo", "KRC": "KRCoin", "KRD": "Krypton DAO", @@ -6755,6 +7734,8 @@ "KRUGERCOIN": "KrugerCoin", "KRX": "RAVN Korrax", "KRY": "Krypdraw", + "KRYP": "Krypto Trump", + "KS": "kittyspin", "KS2": "Kingdomswap", "KSC": "KStarCoin", "KSH": "Kahsh", @@ -6774,6 +7755,7 @@ "KTN": "Kattana", "KTO": "Kounotori", "KTON": "Darwinia Commitment Token", + "KTR": "Kitty Run", "KTS": "Klimatas", "KTT": "K-Tune", "KTX": "KwikTrust", @@ -6788,6 +7770,7 @@ "KUMU": "Kumu Finance", "KUNAI": "KunaiKash", "KUNCI": "Kunci Coin", + "KUNDALINI": "Kundalini is a real girl", "KUR": "Kuro", "KURO": "Kurobi", "KURT": "Kurrent", @@ -6815,6 +7798,7 @@ "KYCC": "KYCCOIN", "KYL": "Kylin Network", "KYOKO": "Kyoko", + "KYRA": "KYRA", "KYTE": "Kambria Yield Tuning Engine", "KYUB": "Kyuubi", "KYVE": "KYVE Network", @@ -6823,13 +7807,16 @@ "L": "L inu", "L2": "Leverj Gluon", "L2DAO": "Layer2DAO", + "L3": "Layer3", "L3P": "Lepricon", "L3USD": "L3USD", "L7": "L7", "LA": "LATOKEN", "LAB": "Labrys", + "LABORCRYPTO": "LaborCrypto", "LABRA": "LabraCoin", "LABS": "LABS Group", + "LABUBU": "Labubu", "LABX": "Stakinglab", "LABZ": "Insane Labz", "LACCOIN": "LocalAgro", @@ -6840,6 +7827,7 @@ "LADYS": "Milady Meme Coin", "LAEEB": "LaEeb", "LAELAPS": "Laelaps", + "LAFFIN": "Laffin Kamala", "LAI": "LayerAI", "LAIKA": "Laika Protocol", "LAINESOL": "Laine Staked SOL", @@ -6852,20 +7840,25 @@ "LANC": "Lanceria", "LAND": "Landshare", "LANDB": "LandBox", + "LANDLORD": "LANDLORD RONALD", "LANDS": "Two Lands", "LANDV1": "Landshare v1", "LANDWOLF": "LANDWOLF", + "LANDWOLFETH": "Landwolf", "LANDWU": "LandWu", "LANE": "LaneAxis", "LAO": "LC Token", + "LAOS": "LAOS Network", "LAPI": "Lapis Inu", "LAPTOP": "Hunter Biden's Laptop", + "LAPUPU": "Lapupu", "LAR": "LinkArt", "LARIX": "Larix", "LARO": "Anito Legends", "LARR": "larrywifhat", "LARRY": "LarryCoin", "LAS": "LNAsolution Coin", + "LASOL": "LamaSol", "LAT": "PlatON Network", "LATOM": "Liquid ATOM", "LATTE": "LatteSwap", @@ -6877,10 +7870,13 @@ "LAVE": "Lavandos", "LAVITA": "Lavita AI", "LAW": "Law Token", + "LAWO": "Law Of Attraction", "LAX": "LAPO", + "LAY3R": "AutoLayer", "LAYER": "UniLayer", "LAZ": "Lazarus", "LAZIO": "Lazio Fan Token", + "LAZYCAT": "LAZYCAT", "LB": "LoveBit", "LBA": "Cred", "LBC": "LBRY Credits", @@ -6888,9 +7884,10 @@ "LBL": "LABEL Foundation", "LBLOCK": "Lucky Block", "LBM": "Libertum", - "LBR": "LaborCrypto", + "LBR": "Lybra Finance", + "LBRV1": "Lybra Finance v1", "LBT": "Law Blocks", - "LBTC": "LiteBitcoin", + "LBTC": "Lombard Staked BTC", "LBXC": "LUX BIO EXCHANGE COIN", "LC": "Lotus Capital", "LC4": "LEOcoin", @@ -6898,11 +7895,12 @@ "LCC": "LitecoinCash", "LCD": "Lucidao", "LCG": "LCG", + "LCI": "LOVECHAIN", "LCK": "Luckbox", "LCMG": "ElysiumG", "LCMS": "LCMS", "LCP": "Litecoin Plus", - "LCR": "Lucre", + "LCR": "Lucro", "LCRO": "Liquid CRO", "LCS": "LocalCoinSwap", "LCSN": "Lacostoken", @@ -6930,6 +7928,7 @@ "LEE": "Love Earn Enjoy", "LEET": "LeetSwap", "LEG": "Legia Warsaw Fan Token", + "LEGION": "LEGION", "LEGO": "Lego Coin", "LEIA": "Leia", "LELE": "Lelecoin", @@ -6938,6 +7937,7 @@ "LEMN": "LEMON", "LEMO": "LemoChain", "LEMON": "LemonCoin", + "LEMX": "LEMON", "LEN": "Liqnet", "LENARD": "Lenard", "LEND": "Aave", @@ -6945,17 +7945,22 @@ "LENDS": "Lends", "LENFI": "Lenfi", "LENIN": "LeninCoin", + "LENS": "Len Sassaman (len-sassaman.vip)", "LEO": "LEO Token", + "LEOCOIN": "LEO", "LEOPARD": "Leopard", "LEOS": "Leonicorn Swap", "LEOX": "Galileo", "LEPA": "Lepasa", "LEPEN": "LePenCoin", + "LEPER": "Leper", "LESBIAN": "Lesbian Inu", "LESS": "Less Network", "LESSF": "LessFnGas", + "LESTER": "Litecoin Mascot", "LET": "LinkEye", "LETIT": "Letit", + "LETS": "Let's WIN This", "LETSGO": "Lets Go Brandon", "LEU": "CryptoLEU", "LEV": "Levante U.D. Fan Token", @@ -6974,6 +7979,7 @@ "LFG": "Gamerse", "LFGO": "Lets Fuckin Go", "LFI": "LunaFi", + "LFIT": "LFIT", "LFNTY": "Lifinity", "LFT": "Lend Flare Dao", "LFW": "Linked Finance World", @@ -6982,6 +7988,7 @@ "LGC": "LiveGreen Coin", "LGCY": "LGCY Network", "LGD": "Legends Cryptocurrency", + "LGNDX": "LegendX", "LGO": "Legolas Exchange", "LGOLD": "LYFE GOLD", "LGOT": "LGO Token", @@ -7002,14 +8009,17 @@ "LIBRE": "Libre", "LIC": "Ligercoin", "LICK": "PetLFG", + "LICKER": "LICKER", "LICO": "Liquid Collectibles", "LID": "Liquidity Dividends Protocol", "LIDER": "Lider Token", + "LIE": "it’s all a lie", "LIEN": "Lien", "LIF": "Winding Tree", "LIF3": "LIF3", "LIFE": "Life Crypto", "LIFEBIRD": "LIFEBIRD", + "LIFET": "LifeTime", "LIFETOKEN": "LIFE", "LIFT": "Uplift", "LIGER": "Ligercoin", @@ -7019,10 +8029,13 @@ "LIKE": "Only1", "LIKEC": "LikeCoin", "LILA": "LiquidLayer", + "LILB": "Lil Brett", "LILFLOKI": "Lil Floki", "LILPUMP": "lilpump", + "LILY": "LILY-The Gold Digger", "LIME": "iMe Lab", "LIMEX": "Limestone Network", + "LIMITEDCOIN": "Limited Coin", "LIMO": "Limoverse", "LIMX": "LimeCoinX", "LINA": "Linear", @@ -7039,6 +8052,7 @@ "LINSPIRIT": "linSpirit", "LINU": "Luna Inu", "LINX": "Linx", + "LIO": "Lio", "LION": "Lion Token", "LIPC": "LIpcoin", "LIPS": "LipChain", @@ -7046,13 +8060,16 @@ "LIQD": "Liquid Finance", "LIQR": "Topshelf Finance", "LIQUI": "Liquidus", + "LIQUIDIUM": "LIQUIDIUM•TOKEN", "LIR": "Let it Ride", "LIS": "Realis Network", "LISA": "Lisa Simpson", "LIST": "KList Protocol", "LISTA": "Lista DAO", + "LISUSD": "lisUSD", "LIT": "Litentry", "LITE": "Lite USD", + "LITEBTC": "LiteBitcoin", "LITENETT": "Litenett", "LITH": "Lithium Finance", "LITHIUM": "Lithium", @@ -7061,6 +8078,7 @@ "LITT": "LitLab Games", "LIV": "LiviaCoin", "LIVE": "TRONbetLive", + "LIVESEY": "Dr. Livesey", "LIVESTARS": "Live Stars", "LIXX": "Libra Incentix", "LIZ": "Lizus Payment", @@ -7088,6 +8106,8 @@ "LMCH": "Latamcash", "LMCSWAP": "LimoCoin SWAP", "LMEOW": "lmeow", + "LMF": "Lamas Finance", + "LMQ": "Lightning McQueen", "LMR": "Lumerin", "LMT": "Lympo Market Token", "LMTOKEN": "LM Token", @@ -7109,6 +8129,7 @@ "LNX": "Lunox Token", "LOA": "League of Ancients", "LOAF": "LOAF CAT", + "LOAFCAT": "LOAFCAT", "LOAN": "Lendoit", "LOBO": "LOBO•THE•WOLF•PUP", "LOBS": "Lobstex", @@ -7118,6 +8139,7 @@ "LOCG": "LOCGame", "LOCI": "LociCoin", "LOCK": "Contracto", + "LOCKIN": "LOCK IN", "LOCO": "Loco", "LOCOM": "Locomotir", "LOCUS": "Locus Chain", @@ -7127,15 +8149,20 @@ "LOFI": "LOFI", "LOG": "Wood Coin", "LOGO": "LOGOS", + "LOGX": "LogX Network", "LOIS": "Lois Token", "LOKA": "League of Kingdoms", "LOKR": "Polkalokr", "LOL": "EMOGI Network", "LOLA": "Lola", + "LOLATHECAT": "Lola", "LOLC": "LOL Coin", + "LOLLY": "Lollipop", + "LOLO": "Lolo", "LON": "Tokenlon", "LONG": "Longdrink Finance", "LONGFU": "LONGFU", + "LONGM": "Long Mao", "LONGSHINE": "LongShine", "LOOK": "LookCoin", "LOOKS": "LooksRare", @@ -7144,6 +8171,7 @@ "LOON": "Loon Network", "LOONG": "PlumpyDragons", "LOOP": "LOOP", + "LOOPIN": "LooPIN Network", "LOOPY": "Loopy", "LOOT": "LootBot", "LOOTEX": "Lootex", @@ -7163,6 +8191,7 @@ "LOTTY": "Lotty", "LOTUS": "The White Lotus", "LOUD": "Loud Market", + "LOULOU": "LOULOU", "LOV": "LoveChain", "LOVE": "Deesse", "LOVELY": "Lovely finance", @@ -7177,6 +8206,7 @@ "LPI": "LPI DAO", "LPK": "Kripton", "LPL": "LinkPool", + "LPM": "Love Power Market", "LPNT": "Luxurious Pro Network Token", "LPOOL": "Launchpool", "LPT": "Livepeer", @@ -7190,10 +8220,14 @@ "LQDR": "LiquidDriver", "LQDX": "Liquid Crypto", "LQR": "Laqira Protocol", + "LQT": "Lifty", "LQTY": "Liquity", "LRC": "Loopring", + "LRDS": "BLOCKLORDS", "LRG": "Largo Coin", "LRN": "Loopring [NEO]", + "LRT": "LandRocker", + "LRT2": "LRT Squared", "LSC": "LS Coin", "LSD": "LightSpeedCoin", "LSDOGE": "LSDoge", @@ -7224,7 +8258,7 @@ "LTCR": "LiteCreed", "LTCU": "LiteCoin Ultra", "LTCX": "LitecoinX", - "LTD": "Limited Coin", + "LTD": "Living the Dream", "LTE": "Local Token Exchange", "LTEX": "Ltradex", "LTG": "LiteCoin Gold", @@ -7238,6 +8272,7 @@ "LTPC": "Lightpaycoin", "LTR": "LogiTron", "LTRBT": "Little Rabbit", + "LTRBTV1": "Little Rabbit v1", "LTS": "Litestar Coin", "LTT": "LocalTrade", "LTX": "Lattice Token", @@ -7247,12 +8282,15 @@ "LUBE": "Joe Lube Coin", "LUC": "Play 2 Live", "LUCA": "LUCA", + "LUCE": "Luce", "LUCHOW": "LunaChow", + "LUCI": "LUCI", "LUCK": "Lucky Cat", "LUCKY": "Lucky Lion", "LUCKYB": "LuckyBlocks", "LUCKYS": "LuckyStar", "LUCKYSLP": "LuckysLeprecoin", + "LUCRE": "Lucre", "LUCY": "Lucy", "LUDO": "Ludo", "LUFC": "Leeds United Fan Token", @@ -7265,9 +8303,14 @@ "LUM": "Illuminates", "LUMA": "LUMA Token", "LUMI": "LUMI Credits", + "LUMIA": "Lumia", + "LUMIO": "Solana Mascot", + "LUMOS": "Lumos", "LUN": "Lunyr", "LUNA": "Terra", + "LUNAB": "Luna by Virtuals", "LUNAR": "Lunar", + "LUNARLENS": "Lunarlens", "LUNAT": "Lunatics", "LUNC": "Terra Classic", "LUNCARMY": "LUNCARMY", @@ -7299,8 +8342,10 @@ "LXF": "LuxFi", "LXT": "LITEX", "LXTO": "LuxTTO", + "LYA": "Huralya", "LYB": "LyraBar", "LYC": "LycanCoin", + "LYDI": "Lydia Finance", "LYF": "Lillian Token", "LYFE": "Lyfe", "LYK": "Loyakk Vega", @@ -7321,6 +8366,7 @@ "LYZI": "Lyzi", "LZ": "LaunchZone", "LZM": "LoungeM", + "LZUSDC": "LayerZero Bridged USDC (Fantom)", "M": "MetaVerse-M", "M1": "SupplyShock", "M2O": "M2O Token", @@ -7330,22 +8376,30 @@ "MAC": "MachineCoin", "MACHO": "macho", "MADA": "MilkADA", + "MADAGASCARTOKEN": "Madagascar Token", "MADANA": "MADANA", "MADC": "MadCoin", + "MADH": "Madhouse", "MADOG": "MarvelDoge", + "MADP": "Mad Penguin", "MADPEPE": "Mad Pepe", "MAEP": "Maester Protocol", "MAF": "MetaMAFIA", - "MAG": "Magnet", + "MAG": "Magnify Cash", "MAGA": "MAGA Hat", "MAGA2024": "MAGA2024", "MAGAA": "MAGA AGAIN", + "MAGAC": "MAGA CAT", + "MAGACA": "MAGA CAT", "MAGACAT": "MAGACAT", "MAGADOGE": "MAGA DOGE", "MAGAIBA": "Magaiba", + "MAGAN": "Maganomics On Solana", "MAGANOMICS": "Maganomics", + "MAGAP": "MAGA PEPE", "MAGAPEPE": "MAGA PEPE", "MAGASHIB": "MAGA SHIB", + "MAGASOL": "MAGA", "MAGATRUMP": "MAGA Trump", "MAGE": "MetaBrands", "MAGIC": "Magic", @@ -7353,7 +8407,13 @@ "MAGICK": "Cosmic Universe Magick", "MAGICV": "Magicverse", "MAGIK": "Magik Finance", + "MAGN": "Magnate Finance", "MAGNET": "Yield Magnet", + "MAGNET6900": "MAGNET6900", + "MAGNETWORK": "Magnet", + "MAGOA": "Make America Great Once Again", + "MAGPAC": "MAGA Meme PAC", + "MAH": "Mahabibi Bin Solman", "MAHA": "MahaDAO", "MAI": "Mindsync", "MAIA": "Maia", @@ -7361,7 +8421,11 @@ "MAIL": "CHAINMAIL", "MAINSTON": "Ston", "MAJO": "Majo", + "MAJOR": "Major Frog", + "MAK": "MetaCene", "MAKE": "MAKE", + "MAKEA": "Make America Healthy Again", + "MAKEE": "Make Ethereum Great Again", "MAKI": "MakiSwap", "MALGO": "milkALGO", "MALL": "Metamall", @@ -7371,12 +8435,14 @@ "MAN": "Matrix AI Network", "MANA": "Decentraland", "MANC": "Mancium", + "MAND": "Mandala Exchange Token", "MANDALA": "Mandala Exchange Token", "MANDOX": "MandoX", "MANE": "MANE", "MANEKI": "MANEKI", "MANGA": "Manga Token", "MANIA": "ScapesMania", + "MANIFEST": "Manifest", "MANNA": "Manna", "MANORUKA": "ManoRuka", "MANT": "Mantle USD", @@ -7384,6 +8450,7 @@ "MANTLE": "Mantle", "MANYU": "Little Manyu", "MAO": "Mao", + "MAOW": "MAOW", "MAP": "MAP Protocol", "MAPC": "MapCoin", "MAPE": "Mecha Morphing", @@ -7391,6 +8458,7 @@ "MAPS": "MAPS", "MAR3": "Mar3 AI", "MARCO": "MELEGA", + "MARCUS": "Marcus Cesar Inu", "MARE": "Mare Finance", "MARGA": "Margaritis", "MARGINLESS": "Marginless", @@ -7406,6 +8474,7 @@ "MARS4": "MARS4", "MARSC": "MarsCoin", "MARSH": "Unmarshal", + "MARSO": "Marso.Tech", "MARSRISE": "MarsRise", "MARSUPILAMI": "MARSUPILAMI INU", "MARSW": "Marswap", @@ -7430,6 +8499,7 @@ "MASTER": "Mastercoin", "MASTERCOIN": "MasterCoin", "MASTERMINT": "MasterMint", + "MASTERTRADER": "MasterTraderCoin", "MASYA": "MASYA", "MAT": "MiniApps", "MATA": "Ninneko", @@ -7441,18 +8511,26 @@ "MATICX": "Stader MaticX", "MATPAD": "MaticPad", "MATRIX": "Matrix Labs", + "MATT": "Matt Furie", + "MATTER": "AntiMatter", "MAU": "MAU", "MAUW": "MAUW", "MAV": "Maverick Protocol", + "MAVAX": "Avalanche (Multichain)", "MAVIA": "Heroes of Mavia", "MAW": "Mountain Sea World", - "MAX": "MaxCoin", + "MAWA": "Kumala Herris", + "MAWC": "Magawincat", + "MAX": "Matr1x", + "MAXCOIN": "MaxCoin", + "MAXETH": "Max on ETH", "MAXI": "Maximus", "MAXL": "Maxi protocol", "MAXR": "Max Revive", "MAXX": "MAXX Finance", "MAY": "Theresa May Coin", "MAYACOIN": "MayaCoin", + "MAYO": "Mr Mayonnaise the Cat", "MAYP": "Maya Preferred", "MAZC": "MyMazzu", "MAZI": "MaziMatic", @@ -7460,6 +8538,8 @@ "MB": "MineBee", "MB4": "Matthew Box 404", "MB8": "MB8 Coin", + "MBAG": "MoonBag", + "MBANK": "MetaBank", "MBAPEPE": "MBAPEPE", "MBASE": "Minebase", "MBC": "MicroBitcoin", @@ -7470,12 +8550,16 @@ "MBET": "MoonBet", "MBF": "MoonBear.Finance", "MBI": "Monster Byte Inc", + "MBID": "myBID", + "MBILLY": "MAMA BILLY", "MBIT": "Mbitbooks", "MBL": "MovieBloc", "MBLC": "Mont Blanc", "MBLK": "Magical Blocks", + "MBLV1": "MovieBloc v1", "MBM": "MobileBridge Momentum", "MBN": "Mobilian Coin", + "MBNB": "Binance Coin (Multichain)", "MBONK": "megaBonk", "MBOT": "MoonBot", "MBOX": "MOBOX", @@ -7487,7 +8571,9 @@ "MBTX": "MinedBlock", "MBX": "Marblex", "MC": "Merit Circle", + "MCA": "Mcashchain", "MCADE": "Metacade", + "MCAKE": "EasyCake", "MCAP": "MCAP", "MCAR": "MasterCar", "MCASH": "Monsoon Finance", @@ -7496,7 +8582,10 @@ "MCB": "MCDEX", "MCC": "Magic Cube Coin", "MCD": "CDbio", + "MCDAI": "Dai (Multichain)", + "MCDULL": "McDull", "MCELO": "Moola Celo", + "MCEN": "Main Character Energy", "MCEUR": "Moola Celo EUR", "MCF": "MCFinance", "MCG": "MicroChains Gov Token", @@ -7509,6 +8598,7 @@ "MCN": "mCoin", "MCO": "Crypto.com", "MCO2": "Moss Carbon Credit", + "MCOI": "MCOIN", "MCOIN": "MCOIN", "MCONTENT": "MContent", "MCP": "My Crypto Play", @@ -7518,10 +8608,12 @@ "MCRT": "MagicCraft", "MCS": "MCS Token", "MCT": "MyConstant", + "MCTO": "McToken", "MCTP": "Metacraft", "MCU": "MediChain", "MCUSD": "Moola Celo USD", "MCV": "MCV Token", + "MD": "MetaDeck", "MDA": "Moeda", "MDAI": "MindAI", "MDAO": "MarsDAO", @@ -7535,6 +8627,7 @@ "MDICE": "Multidice", "MDM": "Medium", "MDN": "Modicoin", + "MDOGE": "First Dog In Mars", "MDR": "Mudra MDR", "MDS": "MediShares", "MDT": "Measurable Data Token", @@ -7557,6 +8650,7 @@ "MEDIC": "MedicCoin", "MEDICO": "Mediconnect", "MEDIT": "MediterraneanCoin", + "MEDUSA": "MEDUSA", "MEE": "Medieval Empires", "MEED": "Meeds DAO", "MEER": "Qitmeer Network", @@ -7566,17 +8660,22 @@ "MEFA": "Metaverse Face", "MEGA": "MegaFlash", "MEGABOT": "Megabot", + "MEGAD": "Mega Dice Casino", "MEGAHERO": "MEGAHERO", "MEGALAND": "Metagalaxy Land", "MEGALANDV1": "Metagalaxy Land v1", + "MEGAX": "Megahex", "MEGE": "MEGE", "MEH": "meh", + "MEI": "Mei Solutions", + "MEIZHU": "GUANGZHOU ZOO NEW BABY PANDA", "MEL": "MELX", "MELANIA": "Melania Trump", "MELB": "Minelab", "MELD": "MELD", "MELI": "Meli Games", "MELLO": "Mello Token", + "MELLOW": "Mellow Man", "MELLSTROY": "MELLSTROY", "MELO": "Melo Token", "MELODITY": "Melodity", @@ -7592,18 +8691,24 @@ "MEMEETF": "Meme ETF", "MEMEFI": "MemeFi", "MEMEINU": "Meme Inu", + "MEMEM": "Meme Man", "MEMEME": "MEMEME", "MEMEMINT": "MEME MINT", "MEMEMUSK": "MEME MUSK", "MEMERUNE": "MEME•ECONOMICS", "MEMES": "MemeCoinDAO", + "MEMESAI": "Memes AI", + "MEMESQUAD": "Meme Squad", "MEMET": "MEMETOON", "MEMETIC": "Memetic", "MEMORYCOIN": "MemoryCoin", + "MEN": "METAHUB FINANCE", "MENDI": "Mendi Finance", "MENGO": "Flamengo Fan Token", "MENLO": "Menlo One", + "MEO": "Meow Of Meme", "MEOW": "Zero Tech", + "MEOWETH": "Meow", "MEOWG": "MeowGangs", "MEOWIF": "Meowifhat", "MEOWM": "Meow Meow Coin", @@ -7619,6 +8724,7 @@ "MERIDIAN": "Meridian Network LOCK", "MERKLE": "Merkle Network", "MERL": "Merlin Chain", + "MERLIN": "Oldest Raccoon", "MERY": "Mistery On Cro", "MESA": "MetaVisa", "MESG": "MESG", @@ -7626,11 +8732,16 @@ "MESSI": "MESSI COIN", "MESSU": "Loinel Messu", "MET": "Metronome", - "META": "Metadium", + "META": "MetaDAO", + "METAA": "META ARENA", "METABOT": "Robot Warriors", "METAC": "Metacoin", + "METACA": "MetaCash", "METACAT": "MetaCat", + "METACLOUD": "Metacloud", "METACR": "Metacraft", + "METAD": "MetaDoge", + "METADIUM": "Metadium", "METADOGE": "MetaDoge", "METADOGEV2": "MetaDoge V2", "METAF": "MetaFastest", @@ -7646,13 +8757,15 @@ "METAQ": "MetaQ", "METAS": "Metaseer", "METAT": "MetaTrace", + "METATI": "Metatime Coin", + "METATR": "MetaTrace Utility Token", "METAUFO": "MetaUFO", "METAV": "MetaVPad", + "METAVE": "Metaverse Convergence", "METAVIE": "Metavie", "METAW": "MetaWorth", "METAX": "MetaverseX", "METEOR": "Meteorite Network", - "METER": "Meter Stable", "METFI": "MetFi", "METH": "Mantle Staked Ether", "METI": "Metis", @@ -7663,6 +8776,7 @@ "METRO": "Metropoly", "MEU": "MetaUnit", "MEV": "MEVerse", + "MEVETH": "mevETH", "MEVR": "Metaverse VR", "MEW": "cat in a dogs world", "MEWC": "Meowcoin", @@ -7686,10 +8800,12 @@ "MFPS": "Meta FPS", "MFS": "Moonbase File System", "MFT": "Hifi Finance (Old)", + "MFTM": "Fantom (Multichain)", "MFTU": "Mainstream For The Underground", "MFUND": "Memefund", "MFX": "MFChain", "MG": "MinerGate Token", + "MG8": "Megalink", "MGAMES": "Meme Games", "MGAR": "Metagame Arena", "MGC": "Meta Games Coin", @@ -7721,8 +8837,10 @@ "MIC": "Mithril Cash", "MICE": "Mice", "MICHI": "michi", + "MICK": "Mickey Meme", "MICKEY": "Steamboat Willie", "MICRO": "Micromines", + "MICRODOGE": "MicroDoge", "MIDAI": "Midway AI", "MIDAS": "Midas", "MIDASDOLLAR": "Midas Dollar Share", @@ -7730,15 +8848,21 @@ "MIE": "MIE Network", "MIF": "monkeywifhat", "MIG": "Migranet", + "MIGGLEI": "Migglei", + "MIGGLES": "Mr Miggles", "MIGMIG": "MigMig Swap", + "MIHARU": "Smiling Dolphin", "MIIDAS": "Miidas NFT", "MIININGNFT": "MiningNFT", + "MIKE": "Mike", "MIKS": "MIKS COIN", "MIL": "Milllionaire Coin", + "MILA": "MILADY MEME TOKEN", "MILE": "milestoneBased", "MILEI": "MILEI", "MILK": "Milkshake Swap", "MILK2": "Spaceswap MILK2", + "MILKBAG": "MILKBAG", "MILKYWAY": "MilkyWayZone", "MILLI": "Million", "MILLY": "milly", @@ -7746,6 +8870,7 @@ "MILOCEO": "Milo CEO", "MILOCOIN": "MiloCoin", "MILODOG": "MILO DOG", + "MILOP": "MILO Project", "MIM": "Magic Internet Money", "MIMATIC": "MAI", "MIMI": "MIMI Money", @@ -7755,28 +8880,38 @@ "MINA": "Mina Protocol", "MINC": "MinCoin", "MIND": "Morpheus Labs", + "MINDC": "MindCoin", "MINDCOIN": "MindCoin", "MINDEX": "Mindexcoin", "MINDGENE": "Mind Gene", "MINDS": "Minds", "MINE": "SpaceMine", + "MINEA": "Mine AI", "MINER": "MINER", "MINERALS": "Minerals Coin", "MINES": "MINESHIELD", "MINETTE": "Vibe Cat", "MINEX": "Minex", - "MINI": "Mini", + "MINGO": "Mingo", + "MINI": "mini", "MINIBNBTIGER": "MiniBNBTiger", + "MINID": "Mini Donald", "MINIDOGE": "MiniDOGE", "MINIFOOTBALL": "Minifootball", "MINIMYRO": "Mini Myro", + "MININEIRO": "Mini Neiro", + "MININGWATCHDOG": "Miningwatchdog Smartchain", "MINION": "Minions INU", + "MINIP": "MiniPepe Coin", "MINIPEPE": "MiniPepe", + "MINIS": "Mini", "MINISHIB": "miniSHIB ETH", "MINO": "MINO INU", + "MINOCOINCTO": "MINO", "MINS": "Minswap", "MINT": "Mint Club", "MINTCOIN": "MintCoin", + "MINTE": "Minter HUB", "MINTME": "MintMe.com Coin", "MINTYS": "MintySwap", "MINU": "Minu", @@ -7788,13 +8923,17 @@ "MIR": "Mirror Protocol", "MIRA": "Chains of War", "MIRACLE": "MIRACLE", + "MIRAI": "MIRAI", "MIRC": "MIR COIN", + "MIRT": "MIR Token", "MIS": "Mithril Share", "MISA": "Sangkara", "MISCOIN": "MIScoin", + "MISHA": "Vitalik's Dog", "MISHKA": "Mishka Token", "MISS": "MISS", "MIST": "Mist", + "MISTE": "Mister Miggles", "MISTRAL": "Mistral AI", "MIT": "Galaxy Blitz", "MITC": "MusicLife", @@ -7802,6 +8941,7 @@ "MITHRIL": "CLIMBERS", "MITTENS": "Mittens", "MITX": "Morpheus Infrastructure Token", + "MIU": "Miu", "MIV": "MakeItViral", "MIVA": "Minerva Wallet", "MIVRS": "Minionverse", @@ -7810,26 +8950,32 @@ "MIXCOIN": "Mixaverse", "MIXER": "TON Mixer", "MIY": "Icel Idman Yurdu Token", + "MIZ": "Mizar", "MJT": "MojitoSwap", "MK": "Meme Kombat", "MKC": "Meta Kongz", "MKEY": "MEDIKEY", + "MKL": "Merkle Trade", "MKONG": "MEME KONG", "MKR": "Maker", "MKT": "MikeToken", "MKUSD": "Prisma mkUSD", "ML": "Mintlayer", "MLA": "Moola", + "MLC": "My Lovely Planet", "MLD": "MonoLend", + "MLEO": "LEO Token (Multichain)", "MLGC": "Marshal Lion Group Coin", + "MLINK": "Chainlink (Multichain)", "MLITE": "MeLite", "MLK": "MiL.k", "MLN": "Enzyme", "MLNK": "Malinka", "MLOKY": "MLOKY", + "MLP": "Matrix Layer Protocol", "MLS": "CPROP", "MLT": "MIcro Licensing Coin", - "MLTC": "MultiWallet Coin", + "MLTC": "Litecoin (Multichain)", "MLTPX": "MoonLift Capital", "MLXC": "Marvellex Classic", "MM": "Millimeter", @@ -7837,6 +8983,7 @@ "MMAI": "MetamonkeyAi", "MMAON": "MMAON", "MMAPS": "MapMetrics", + "MMATIC": "Wrapped Polygon (Multichain)", "MMC": "Monopoly Millionaire Control", "MMETA": "Duckie Land Multi Metaverse", "MMF": "MMFinance", @@ -7852,6 +8999,7 @@ "MMT": "Master MIX Token", "MMTM": "Momentum", "MMUI": "MetaMUI", + "MMULTI": "Multichain (via Multichain Cross-Chain Router)", "MMVG": "MEMEVENGERS", "MMX": "MMX", "MMXIV": "MaieutiCoin", @@ -7859,11 +9007,13 @@ "MMY": "Mummy Finance", "MN": "Cryptsy Mining Contract", "MNB": "MoneyBag", + "MNBR": "MN Bridge", "MNC": "MainCoin", "MND": "Mound Token", "MNDCC": "Mondo Community Coin", "MNDE": "Marinade", "MNE": "Minereum", + "MNEE": "MNEE USD Stablecoin ", "MNET": "MINE Network", "MNFT": "Mongol NFT", "MNFTS": "Marvelous NFTs", @@ -7891,6 +9041,7 @@ "MNZ": "Menzy", "MO": "Morality", "MOAC": "MOAC", + "MOAI": "MOAI", "MOAR": "Moar Finance", "MOAT": "Mother Of All Tokens", "MOB": "MobileCoin", @@ -7898,12 +9049,17 @@ "MOBIC": "Mobility Coin", "MOBIE": "MobieCoin", "MOBILE": "Helium Mobile", + "MOBIU": "Mobius Money", "MOBU": "MOBU", "MOBX": "MOBIX", "MOBY": "Moby", + "MOBYONBASE": "Moby", + "MOBYONBASEV1": "Moby v1", "MOC": "Mossland", + "MOCA": "Moca Coin", "MOCHI": "Mochiswap", "MOCHICAT": "MochiCat", + "MOCK": "Mock Capital", "MOCO": "MoCo", "MOD": "Modefi", "MODA": "MODA DAO", @@ -7922,18 +9078,25 @@ "MOFI": "MobiFi", "MOFOLD": "Molecular Future (ERC20)", "MOG": "Mog Coin", + "MOGC": "MOG CAT", + "MOGCO": "Mog Coin (mogcoinspl.com)", "MOGE": "Moge", "MOGGO": "MOGGO", + "MOGP": "MOG PEPE", + "MOGT": "MOG TRUMP", "MOGU": "Mogu", "MOGUL": "Mogul Productions", + "MOGUT": "Mogutou", "MOGX": "Mogu", "MOH": "Medal of Honour", "MOI": "MyOwnItem", "MOIN": "MoinCoin", + "MOJI": "Moji", "MOJO": "Mojocoin", "MOK": "MocktailSwap", "MOL": "Molecule", "MOLA": "MoonLana", + "MOLI": "Mobile Liquidity", "MOLK": "Mobilink Token", "MOLLARS": "MollarsToken", "MOLLY": "Molly", @@ -7947,6 +9110,7 @@ "MONARCH": "TRUEMONARCH", "MONAV": "Monavale", "MONB": "MonbaseCoin", + "MONDO": "mondo", "MONETA": "Moneta", "MONEY": "MoneyCoin", "MONEYBEE": "MONEYBEE", @@ -7957,6 +9121,7 @@ "MONG20": "Mongoose 2.0", "MONGBNB": "MongBNB", "MONGOOSE": "Mongoose", + "MONGY": "Mongy", "MONI": "Monsta Infinite", "MONIE": "Infiblue World", "MONK": "Monkey Project", @@ -7966,25 +9131,37 @@ "MONKEYS": "Monkeys Token", "MONKU": "Monku", "MONO": "MonoX", + "MONOLITH": "Monolith", "MONONOKEINU": "Mononoke Inu", + "MONOPOLY": "Meta Monopoly", "MONS": "Monsters Clan", "MONST": "Monstock", "MONSTA": "Cake Monster", + "MONSTE": "Monster", "MONT": "Monarch Token", "MONTE": "Monte", "MOO": "MooMonster", + "MOOBIFI": "Staked BIFI", "MOOCAT": "MooCat", + "MOODENG": "Moo Deng (moodengsol.com)", + "MOODENGSPACE": "MOO DENG", + "MOODENGWIF": "MOODENGWIF", "MOOI": "Moonai", "MOOLA": "Degen Forest", "MOOLYA": "moolyacoin", "MOON": "r/CryptoCurrency Moons", "MOONARCH": "Moonarch", + "MOONB": "Moon Base", + "MOONBI": "Moonbix", + "MOONBIX": "MOONBIX MEME", "MOONC": "MoonCoin", "MOOND": "Dark Moon", "MOONDAY": "Moonday Finance", + "MOONDO": "MOON DOGE", "MOONED": "MoonEdge", "MOONER": "CoinMooner", "MOONEY": "Moon DAO", + "MOONI": "MOON INU", "MOONION": "Moonions", "MOONKIZE": "MoonKize", "MOONLIGHT": "Moonlight Token", @@ -7992,6 +9169,7 @@ "MOONS": "Sailor Moons", "MOONSHOT": "Moonshot", "MOONSTAR": "MoonStar", + "MOONW": "moonwolf.io", "MOOO": "Hashtagger", "MOOV": "dotmoovs", "MOOX": "Moox Protocol", @@ -7999,9 +9177,11 @@ "MOR": "Morpheus", "MORA": "Meliora", "MORE": "More Coin", + "MOREGEN": "MoreGen FreeMoon", "MORFEY": "Morfey", "MOROS": "MOROS NET", "MORPH": "Morpheus Token", + "MORPHO": "Morpho", "MORRA": "Morra", "MORSE": "Morse", "MOS": "MOS Coin", @@ -8009,6 +9189,7 @@ "MOT": "Olympus Labs", "MOTA": "MotaCoin", "MOTG": "MetaOctagon", + "MOTH": "MOTH", "MOTHER": "Mother Iggy", "MOTI": "Motion", "MOTO": "Motocoin", @@ -8017,15 +9198,19 @@ "MOVD": "MOVE Network", "MOVE": "MarketMove", "MOVER": "Mover", + "MOVEUSD": "MoveMoney USD", "MOVEY": "Movey", "MOVEZ": "MoveZ", "MOVON": "MovingOn Finance", "MOVR": "Moonriver", "MOW": "mouse in a cats world", "MOWA": "Moniwar", + "MOXIE": "Moxie", + "MOYA": "MOYA", "MOZ": "Mozik", "MP": "Membership Placeholders", "MP3": "MP3", + "MPAA": "MPAA", "MPAD": "MultiPad", "MPAY": "Menapay", "MPC": "Metaplace", @@ -8056,12 +9241,14 @@ "MRHB": "MarhabaDeFi", "MRI": "Marshall Inu", "MRK": "MARK.SPACE", + "MRM": "Mr Mint", "MRN": "Mercoin", "MRNA": "Moderna", "MRP": "MorpheusCoin", "MRPEPE": "Pepe Potato", "MRS": "Metars Genesis", "MRSA": "MrsaCoin", + "MRSMIGGLES": "Mrs Miggles", "MRT": "MinersReward", "MRUN": "Metarun", "MRV": "Macroverse", @@ -8071,10 +9258,11 @@ "MRY": "MurrayCoin", "MSA": "My Shiba Academia", "MSB": "Misbloc", - "MSC": "Miningwatchdog Smartchain", + "MSC": "Matrix SmartChain", "MSCP": "Moonscape", "MSCT": "MUSE ENT NFT", "MSD": "MSD", + "MSFT": "Microsoft 6900", "MSG": "MsgSender", "MSGO": "MetaSetGO", "MSHD": "MASHIDA", @@ -8085,6 +9273,7 @@ "MSOL": "Marinade Staked SOL", "MSOT": "BTour Chain", "MSP": "Mothership", + "MSPC": "MeowSpace", "MSQ": "MSquare Global", "MSR": "Masari", "MST": "Idle Mystic", @@ -8092,12 +9281,13 @@ "MSTETH": "Eigenpie mstETH", "MSTO": "Millennium Sapphire", "MSU": "MetaSoccer", + "MSUSHI": "Sushi (Multichain)", "MSWAP": "MoneySwap", "MT": "MyToken", "MTA": "Meta", "MTB": "MetaBridge", "MTBC": "Metabolic", - "MTC": "MEDICAL TOKEN CURRENCY", + "MTC": "Matrix Chain", "MTCMN": "MTC Mesh", "MTCN": "Multiven", "MTD": "Minted", @@ -8113,11 +9303,13 @@ "MTK": "Moya Token", "MTL": "Metal", "MTLM3": "Metal Music v3", + "MTLS": "eMetals", "MTLX": "Mettalex", + "MTMS": "MTMS Network", "MTN": "TrackNetToken", "MTO": "Merchant Token", "MTP": "Macro Protocol", - "MTR": "MasterTraderCoin", + "MTR": "Meter Stable", "MTRA": "MetaRare", "MTRC": "ModulTrade", "MTRG": "Meter", @@ -8137,25 +9329,31 @@ "MTY": "Viddli", "MTZ": "Monetizr", "MU": "Miracle Universe", + "MUA": "MUA DAO", "MUBI": "Multibit", "MUC": "Multi Universe Central", "MUDOL2": "Hero Blaze: Three Kingdoms", "MUDRA": "MudraCoin", "MUE": "MonetaryUnit", + "MUES": "MuesliSwap MILK", "MULTI": "Multichain", "MULTIBOT": "Multibot", "MULTIGAMES": "MultiGames", "MULTIV": "Multiverse", + "MULTIWALLET": "MultiWallet Coin", "MUMU": "Mumu", "MUN": "MUNcoin", "MUNCH": "Munch Token", "MUNCHY": "Boys Club Munchy", + "MUNI": "Uniswap Protocol Token (Multichain)", "MUNITY": "Metahorse Unity", "MUNK": "Dramatic Chipmunk", + "MUNSUN": "MUNSUN", "MURA": "Murasaki", "MURATIAI": "MuratiAI", "MUSCAT": "MusCat", "MUSD": "mStable USD", + "MUSDC": "USD Coin (Multichain)", "MUSDCOIN": "MUSDcoin", "MUSE": "Muse DAO", "MUSIC": "Gala Music", @@ -8192,10 +9390,13 @@ "MWAVE": "MeshWave", "MWC": "MimbleWimbleCoin", "MWCC": "Metaworld", + "MWD": "MEW WOOF DAO", "MX": "MX Token", "MXC": "Machine Xchange Coin", + "MXD": "Denarius", "MXGP": "MXGP Fan Token", "MXM": "Maximine", + "MXNB": "MXNB", "MXNT": "Tether MXNt", "MXRP": "Monsta XRP", "MXT": "MixTrust", @@ -8215,6 +9416,7 @@ "MYO": "Mycro", "MYOBU": "Myōbu", "MYRA": "Mytheria", + "MYRE": "Myre", "MYRIA": "Myria", "MYRO": "Myro", "MYRODRAGON": "MYRO DRAGON", @@ -8232,18 +9434,23 @@ "MZM": "MetaZooMee", "MZR": "Mazuri GameFi", "MZX": "Mosaic Network", + "Medu": "Medusa", "N0031": "nYFI", "N1": "NFTify", "N3DR": "NeorderDAO ", + "N64": "N64", "N7": "Number7", "N8V": "NativeCoin", "NABOX": "Nabox", "NAC": "Nirvana Chain", + "NACHO": "Nacho the 𐤊at", "NADA": "NADA Protocol Token", "NAFT": "Nafter", "NAH": "Strayacoin", "NAI": "Nuklai", + "NAILONG": "Nailong", "NAKA": "Nakamoto Games", + "NAKAV1": "Nakamoto Games v1", "NALA": "Not a lion, a...", "NALS": "NALS (Ordinals)", "NAM": "Namacoin", @@ -8264,15 +9471,19 @@ "NAS": "Nebulas", "NAS2": "Nas2Coin", "NASADOGE": "Nasa Doge", + "NASDAQ420": "Nasdaq420", "NASH": "NeoWorld Cash", "NASSR": "Alnassr FC Fan Token", "NASTR": "Liquid ASTR", "NAT": "Natmin", + "NATI": "IlluminatiCoin", "NATION": "Nation3", "NATIX": "NATIX Network", + "NATOR": "Pepenator", "NAUSICAA": "Nausicaa-Inu", "NAUT": "Nautilus Coin", "NAV": "NavCoin", + "NAVAL": "NAVAL AI", "NAVC": "NavC token", "NAVI": "Atlas Navi", "NAVIA": "NaviAddress", @@ -8280,7 +9491,9 @@ "NAVX": "NAVI Protocol", "NAVY": "BoatPilot Token", "NAWA": "Narwhale.finance", + "NAWS": "NAWS.AI", "NAX": "NextDAO", + "NAYM": "NAYM", "NAZ": "NAZDAQ", "NAZA": "NAZA", "NAZAR": "NAZAR PROTOCOL", @@ -8306,7 +9519,9 @@ "NCAT": "Neuracat", "NCC": "NeuroChain", "NCDT": "Nuco.Cloud", + "NCN": "NeurochainAI", "NCO": "Nexacore", + "NCOIN": "NatronZ", "NCOP": "NCOP", "NCOR": "NovaCore", "NCORAI": "NeoCortexAI", @@ -8314,6 +9529,7 @@ "NCP": "Newton Coin", "NCR": "Neos Credits", "NCT": "PolySwarm", + "NCTR": "Nectar", "ND": "Nemesis Downfall", "NDAU": "ndau", "NDB": "NDB", @@ -8327,9 +9543,11 @@ "NEADRAM": "The Ennead", "NEAL": "Coineal Token", "NEAR": "Near", + "NEARK": "NearKat", "NEARX": "Stader NearX", "NEAT": "NEAT", "NEBL": "Neblio", + "NEBNB": "Neuro BNB", "NEBU": "Nebuchadnezzar", "NEC": "Nectar", "NEER": "Metaverse.Network Pioneer", @@ -8340,6 +9558,17 @@ "NEFTY": "NeftyBlocks", "NEGED": "Neged", "NEI": "Neurashi", + "NEILUO": "CHINESE NEIRO", + "NEINEI": "Chinese Neiro", + "NEIREI": "NeiRei", + "NEIRO": "Neiro", + "NEIROC": "Neirocoin (neirocoin.club)", + "NEIROCOIN": "Neiro Ethereum", + "NEIROH": "NeiroWifHat", + "NEIROINU": "Neiro Inu", + "NEIROLIVE": "Neiro", + "NEIROLOL": "Neiro", + "NEIROONB": "Neiro on Base", "NEKI": "Neki Token", "NEKO": "The Neko", "NEKOIN": "Nekoin", @@ -8368,6 +9597,8 @@ "NETA": "Negative Tax", "NETC": "NetworkCoin", "NETCOIN": "Netcoincapital", + "NETCOINV1": "Netcoincapital v1", + "NETK": "Netkoin", "NETKO": "Netko", "NETRUM": "Netrum", "NETT": "Netswap", @@ -8384,15 +9615,22 @@ "NEUTR": "Neutrinos", "NEUTRO": "Neutro Protocol", "NEUTRON": "Neutron", + "NEV": "NEVER SURRENDER", "NEVA": "NevaCoin", + "NEVANETWORK": "Neva", + "NEVE": "NEVER SURRENDER", "NEVER": "neversol", "NEW": "Newton", "NEWB": "Newbium", + "NEWBV1": "Newbium v1", + "NEWC": "New Cat", "NEWG": "NewGold", "NEWM": "NEWM", "NEWO": "New Order", "NEWOS": "NewsToken", + "NEWP": "New Peon", "NEWS": "PUBLISH", + "NEWSL": "Newsly", "NEWSTOKENS": "NewsTokens", "NEWTON": "Newtonium", "NEX": "Nash Exchange", @@ -8400,12 +9638,15 @@ "NEXAI": "NexAI", "NEXBOX": "NexBox", "NEXBT": "Native XBTPro Exchange Token", + "NEXG": "NexGami", "NEXM": "Nexum", + "NEXMI": "NexMillionaires", "NEXMS": "NexMillionaires", "NEXO": "NEXO", "NEXT": "Connext Network", "NEXTEX": "Next.exchange Token", "NEXTEXV1": "Next.exchange Token v1", + "NEXTV1": "Connext Network", "NEXUSAI": "NexusAI", "NEXXO": "Nexxo", "NEZHA": "NezhaToken", @@ -8413,7 +9654,9 @@ "NFCR": "NFCore", "NFD": "Feisty Doge NFT", "NFE": "Edu3Labs", + "NFM": "NFMart", "NFN": "Nafen", + "NFNT": "NFINITY AI", "NFP": "NFPrompt", "NFT": "APENFT", "NFT11": "NFT11", @@ -8422,6 +9665,7 @@ "NFTBS": "NFTBooks", "NFTD": "NFTrade", "NFTE": "NFTEarthOFT", + "NFTFI": "NFTfi", "NFTI": "NFT Index", "NFTL": "NFTLaunch", "NFTLOOT": "NFTLootBox", @@ -8429,6 +9673,7 @@ "NFTN": "NFTNetwork", "NFTP": "NFT", "NFTS": "NFT STARS", + "NFTSTYLE": "NFTStyle", "NFTT": "NFT", "NFTX": "NFTX", "NFTXHI": "NFTX Hashmasks Index", @@ -8443,6 +9688,7 @@ "NGL": "Entangle", "NGM": "e-Money", "NGMI": "NGMI Coin", + "NGTG": "NUGGET TRAP", "NHCT": "Nano Healthcare Token", "NHI": "Non Human Intelligence", "NHT": "Neighbourhoods", @@ -8454,17 +9700,24 @@ "NIF": "Unifty", "NIFT": "Niftify", "NIFTSY": "Envelop", + "NIFTY": "Nifty Wizards Dust", + "NIFTYL": "Nifty League", "NIGELLA": "Nigella coin", "NIGHT": "Midnight", "NIGI": "Nigi", + "NIH": "Nihao coin", "NIHAO": "NiHao", "NII": "nahmii", "NIIFI": "NiiFi", "NIK": "NIKPLACE", + "NIKO": "NikolAI", + "NILE": "Nile", "NIM": "Nimiq", "NIMFA": "Nimfamoney", "NIN": "Next Innovation", + "NINJ": "Ninja Protocol", "NINJA": "Dog Wif Nunchucks", + "NINJACAT": "NinjaCat", "NINJAZ": "Danketsu", "NINKY": "Ninky", "NINO": "Ninneko", @@ -8472,7 +9725,10 @@ "NIOB": "Niob Finance", "NIOCTIB": "nioctiB", "NIOX": "Autonio", + "NIOXV1": "Autonio v1", + "NIOXV2": "Autonio v2", "NIPPY": "Cat On Catnip", + "NIQAB": "NIQAB WORLD ORDER", "NIRV": "Nirvana NIRV", "NIT": "Nesten", "NITEFEEDER": "Nitefeeder", @@ -8502,6 +9758,7 @@ "NMH": "Namahe", "NMK": "Namek", "NMKR": "NMKR", + "NML": "No Mans Land", "NMR": "Numeraire", "NMS": "Numus", "NMSP": "Nemesis PRO", @@ -8518,6 +9775,7 @@ "NOBS": "No BS Crypto", "NOCHILL": "AVAX HAS NO CHILL", "NODE": "Whole Network", + "NODIDDY": "NODIDDY", "NODIS": "Nodis", "NODL": "Nodle Network", "NOGS": "Noggles", @@ -8529,8 +9787,13 @@ "NOKU": "NOKU Master token", "NOLA": "Nola", "NOM": "Finom NOM Token", + "NOMNOM": "nomnom", + "NOMOX": "NOMOEX Token", "NONE": "None Trading", "NOO": "Noocoin", + "NOOB": "Blast Royale", + "NOODS": "Noods", + "NOOOO": "NOOOO", "NOOT": "NOOT (Ordinals)", "NOR": "Noir", "NORA": "SnowCrash Token", @@ -8541,7 +9804,11 @@ "NOSN": "nOS", "NOSO": "Noso", "NOT": "Notcoin", + "NOTAI": "NOTAI", + "NOTC": "NOT", + "NOTDOG": "NOTDOG", "NOTE": "Notional Finance", + "NOTECANTO": "Note", "NOTHING": "NOTHING", "NOTINU": "NOTCOIN INU", "NOV": "Novara Calcio Fan Token", @@ -8550,7 +9817,9 @@ "NOX": "NITRO", "NOXB": "Noxbox", "NPAS": "New Paradigm Assets Solution", - "NPC": "NPCcoin", + "NPC": "Non-Playable Coin", + "NPCC": "NPCcoin", + "NPCS": "Non-Playable Coin Solana", "NPER": "NPER", "NPICK": "NPICK BLOCK", "NPLC": "Plus Coin", @@ -8569,7 +9838,7 @@ "NRGY": "NRGY Defi", "NRK": "Nordek", "NRM": "Neuromachine", - "NRN": "Doc.ai Neuron", + "NRN": "Neuron", "NRO": "Neuro", "NRP": "Neural Protocol", "NRS": "NoirShares", @@ -8590,7 +9859,10 @@ "NSP": "NOMAD.space", "NSR": "NuShares", "NSS": "NSS Coin", + "NST": "Ninja Squad Token", "NSTE": "NewSolution 2.0", + "NSTK": "Unstake", + "NSTR": "Nostra", "NSUR": "NSUR Coin", "NSURE": "Nsure Network", "NT": "NEXTYPE Finance", @@ -8602,6 +9874,7 @@ "NTG": "NEWTOWNGAMING", "NTK": "Neurotoken", "NTM": "NetM", + "NTMPI": "Neutaro", "NTO": "Neton", "NTR": "Nether", "NTRN": "Neutron", @@ -8617,6 +9890,7 @@ "NUC": "NuCoin", "NUDE": "0xNude", "NUDES": "NUDES", + "NUGGET": "Gegagedigedagedago", "NUKE": "NukeCoin", "NULS": "Nuls", "NUM": "Numbers Protocol", @@ -8626,8 +9900,10 @@ "NUSA": "Nusa", "NUSD": "Nomin USD", "NUT": "Native Utility Token", + "NUTC": "Nutcash", "NUTGV2": "NUTGAIN", - "NUTS": "Squirrel Finance", + "NUTS": "Thetanuts Finance", + "NUTZ": "NUTZ", "NUUM": "MNet", "NUX": "Peanut", "NVA": "Neeva Defi", @@ -8642,9 +9918,10 @@ "NVT": "NerveNetwork", "NVX": "Novax Coin", "NVZN": "INVIZION", - "NWC": "Newscrypto Coin", + "NWC": "Numerico", "NWCN": "NowCoin", "NWG": "NotWifGary", + "NWIF": "neirowifhat", "NWP": "NWPSolution", "NWS": "Nodewaves", "NXC": "Nexium", @@ -8661,6 +9938,7 @@ "NXTI": "NXTI", "NXTT": "Next Earth", "NXTTY": "NXTTY", + "NYA": "Nya", "NYAN": "NyanCoin", "NYANDOGE": "NyanDOGE International", "NYANTE": "Nyantereum International", @@ -8680,6 +9958,7 @@ "NZL": "Zealium", "NZO": "NonZero", "O": "Childhoods End", + "O1INCH": "1inch (Optimism Bridge)", "O3": "O3 Swap", "O4DX": "O4DX", "OAK": "Acorn Collective", @@ -8689,6 +9968,7 @@ "OAT": "OAT Network", "OATH": "OATH Protocol", "OAX": "Oax", + "OB1INCH": "1inch (OmniBridge)", "OBEMA": "burek obema", "OBI": "Orbofi AI", "OBICOIN": "OBI Real Estate", @@ -8697,11 +9977,14 @@ "OBROK": "OBRok", "OBS": "One Basis Cash", "OBSCURE": "Obscurebay", + "OBSI": "Obsidium", "OBSR": "OBSERVER Coin", + "OBSUSHI": "Sushi (OmniBridge)", "OBT": "Oobit", "OBTC": "Obitan Chain", "OBX": "OpenBlox", "OC": "OrangeCoin", + "OCADA": "OCADA.AI", "OCAI": "Onchain AI", "OCAVU": "Ocavu Network Token", "OCB": "BLOCKMAX", @@ -8714,7 +9997,10 @@ "OCICAT": "OciCat", "OCL": "Oceanlab", "OCN": "Odyssey", + "OCO": "Owners Casino Online", "OCP": "Omni Consumer Protocols", + "OCPR": "OC Protocol", + "OCRV": "Curve DAO Token (OmniBridge)", "OCT": "Octopus Network", "OCTA": "OctaSpace", "OCTAGON": "POLYDeFI", @@ -8726,6 +10012,7 @@ "OCTOIN": "Octoin Coin", "OCW": "Online Cold Wallet", "OCX": "Original Crypto Coin", + "ODAI": "Dai (Optimism Bridge)", "ODC": "Overseas Direct Certification", "ODDZ": "Oddz", "ODE": "ODEM", @@ -8747,7 +10034,10 @@ "OG": "OG Fan Token", "OGCINU": "The OG Cheems Inu", "OGD": "OLYMPIC GAMES DOGE", + "OGGIE": "Oggie", "OGGY": "Oggy Inu", + "OGLG": "OGLONG", + "OGM": "OG Mickey", "OGN": "Origin Protocol", "OGO": "Origo", "OGOD": "GOTOGOD", @@ -8762,15 +10052,19 @@ "OHANDY": "Orbit Bridge Klaytn Handy", "OHM": "Olympus", "OHMV2": "Olympus v2", + "OHNO": "Oh no", + "OHNOGG": "OHNHO (ohno.gg)", "OHO": "OHO", "OICOIN": "Osmium Investment Coin", "OIL": "Oiler", "OILD": "OilWellCoin", + "OILX": "OilX Token", "OIN": "OIN Finance", "OIO": "Online", "OJA": "Ojamu", "OJX": "Ojooo", "OK": "OKCash", + "OKANE": "OKANE", "OKAYEG": "Okayeg", "OKB": "OKB", "OKG": "Ookeenga", @@ -8780,8 +10074,11 @@ "OKS": "Oikos", "OKSE": "Okse", "OKT": "OKT Chain", + "OL": "Open Loot", + "OLAF": "Olaf Token", "OLAND": "Oceanland", "OLAS": "Autonolas", + "OLD": "Old Trump", "OLDSF": "OldSafeCoin", "OLE": "OpenLeverage", "OLEA": "Olea Token", @@ -8793,6 +10090,7 @@ "OLXA": "OLXA", "OLY": "Olyseum", "OLYMP": "OlympCoin", + "OLYMPE": "OLYMPÉ", "OM": "MANTRA", "OMA": "OmegaCoin", "OMAX": "Omax", @@ -8806,12 +10104,15 @@ "OMI": "ECOMI", "OMIC": "Omicron", "OMIKAMI": "Amaterasu Omikami", + "OMIX": "Omix", "OMMI": "Ommniverse", - "OMNI": "Omni", - "OMNIA": "OmniaVerse", + "OMNI": "Omni Network", + "OMNIA": "OMNIA Protocol", + "OMNIAV1": "OmniaVerse v1", + "OMNIAV2": "OmniaVerse", "OMNIC": "OmniCat", "OMNICRON": "OmniCron", - "OMNINET": "Omni Network", + "OMNILAYER": "Omni", "OMNIR": "Omni Real Estate Token", "OMNIX": "OmniBotX", "OMNOM": "Doge Eat Doge", @@ -8819,9 +10120,11 @@ "OMT": "Mars Token", "OMV1": "OM Token (v1)", "OMX": "Project Shivom", + "OMZ": "Open Meta City", "ON": "OFIN Token", "ONAM": "ONAM", "ONC": "One Cash", + "ONCH": "OnchainPoints.xyz", "ONDO": "Ondo", "ONE": "Harmony", "ONES": "OneSwap DAO", @@ -8830,6 +10133,8 @@ "ONG": "SoMee.Social", "ONGAS": "Ontology Gas", "ONI": "ONINO", + "ONIG": "Onigiri", + "ONIGIRI": "Onigiri The Cat", "ONION": "DeepOnion", "ONIT": "ONBUFF", "ONL": "On.Live", @@ -8844,9 +10149,11 @@ "ONUS": "ONUS", "ONX": "Onix", "OOE": "OpenOcean", + "OOFP": "OOFP", "OOGI": "OOGI", "OOKI": "Ooki", "OOKS": "Onooks", + "OOM": "OomerBot", "OORC": "Orbit Bridge Klaytn Orbit Chain", "OORT": "OORT", "OOT": "Utrum", @@ -8855,6 +10162,7 @@ "OPA": "Option Panda Platform", "OPAIG": "OvalPixel", "OPC": "OP Coin", + "OPCA": "OP_CAT(BIP-420)", "OPCAT": "OPCAT", "OPCT": "Opacity", "OPEN": "Open Custody Protocol", @@ -8865,6 +10173,7 @@ "OPENP": "Open Platform", "OPENRI": "Open Rights Exchange", "OPENSOURCE": "Open Source Network", + "OPENW": "OpenWorld", "OPENX": "OpenSwap Optimism Token", "OPEPE": "Optimism PEPE", "OPES": "Opes", @@ -8880,23 +10189,28 @@ "OPS": "Octopus Protocol", "OPSC": "OpenSourceCoin", "OPSEC": "OpSec", + "OPSV1": "Octopus Protocol v1", + "OPSV2": "Octopus Protocol v2", "OPT": "Opus", "OPTA": "Opta Global", "OPTC": "Open Predict Token", "OPTCM": "Optimus", "OPTI": "Optimus AI", "OPTIG": "Catgirl Optimus", + "OPTIM": "Optimus X", "OPTIMOUSE": "Optimouse", "OPTION": "OptionCoin", "OPU": "Opu Coin", "OPUL": "Opulous", "OPV": "OpenLive NFT", "OPXVEVELO": "OpenX Locked Velo", + "ORA": "Oracolxor", "ORACLE": "Oracle AI", "ORACLECHAIN": "OracleChain", "ORACUL": "Oracul Ai", "ORAI": "Oraichain Token", "ORAIX": "OraiDEX", + "ORANGE": "Annoying Orange", "ORAO": "ORAO Network", "ORARE": "OneRare", "ORB": "KlayCity ORB", @@ -8910,9 +10224,11 @@ "ORC": "Orbit Chain", "ORCA": "Orca", "ORD": "ordinex", + "ORDER": "Orderly Network", "ORDI": "Ordinals ", "ORDI2": "ORDI 2.0", "ORDIFI": "OrdinalsFi", + "ORDIN": "ORDINAL HODL MEME", "ORDS": "Ordiswap", "ORE": "Galactrum", "OREO": "OreoFi", @@ -8923,9 +10239,11 @@ "ORI": "Origami", "ORIGIN": "Origin Foundation", "ORION": "Orion Money", + "ORKL": "Orakler", "ORLY": "OrlyCoin", "ORM": "ORIUM", "ORME": "Ormeus Coin", + "ORMO": "Ormolus", "ORN": "Orion Protocol", "ORNJ": "Orange", "ORO": "Operon Origins", @@ -8942,6 +10260,7 @@ "OSA": "OSA Token", "OSAK": "Osaka Protocol", "OSC": "iOscar", + "OSCAR": "OSCAR", "OSEA": "Omnisea", "OSEAN": "OSEAN", "OSETH": "StakeWise Staked ETH", @@ -8952,10 +10271,12 @@ "OSK": "OSK", "OSKDAO": "OSK DAO", "OSL": "OSL AI", + "OSMI": "OSMI", "OSMO": "Osmosis", "OSQTH": "Opyn Squeeth", "OSS": "OSSChain", "OST": "OST", + "OSUSHI": "Sushi (Optimism Bridge)", "OSWAP": "OpenSwap", "OT": "Onchain Trade", "OTB": "OTCBTC Token", @@ -8973,6 +10294,7 @@ "OUSD": "Origin Dollar", "OUSDC": "Orbit Bridge Klaytn USDC", "OUSE": "OUSE Token", + "OUSG": "OUSG", "OUT": "Netscouters", "OVC": "OVCODE", "OVERLORD": "Overlord", @@ -8984,6 +10306,7 @@ "OWL": "OWL Token", "OWN": "Ownly", "OWNDATA": "OWNDATA", + "OWO": "SoMon", "OX": "Open Exchange Token", "OXAI": "OxAI.com", "OXB": "Oxbull Tech", @@ -9004,7 +10327,9 @@ "OZMPC": "Ozempic", "OZO": "Ozone Chain", "OZONE": "Ozone metaverse", + "OZONEC": "Ozonechain", "OZP": "OZAPHYRE", + "P": "PUPS•WORLD•PEACE", "P202": "Project 202", "P2PS": "P2P Solutions Foundation", "P3D": "3DPass", @@ -9014,8 +10339,10 @@ "PABLO": "PABLO DEFI", "PAC": "PacMoon", "PACE": "3space Art", + "PACK": "HashPack", "PACM": "Pacman Blastoff", "PACMAN": "Pac Man", + "PACO": "Paco", "PACOCA": "Pacoca", "PACP": "PAC Protocol", "PACT": "impactMarket", @@ -9031,6 +10358,7 @@ "PAK": "Pakcoin", "PAL": "PolicyPal Network", "PALAI": "PaladinAI", + "PALE": "Palette", "PALET": "Palette", "PALG": "PalGold", "PALLA": "Pallapay", @@ -9041,26 +10369,34 @@ "PAM": "PAM", "PAMBI": "Pambicoin", "PAMP": "PAMP Network", - "PAN": "Pantos", + "PAN": "Pankito", "PAND": "Panda Finance", "PANDA": "PandaDAO", "PANDAI": "PandAI", + "PANDAS": "Panda Swap", + "PANDE": "Pande", "PANDO": "Pando", "PANDOP": "PandoProject", "PANDORA": "Pandora", "PANGEA": "PANGEA", "PANIC": "PanicSwap", "PANO": "PanoVerse", + "PANTOS": "Pantos", + "PAO": "South Pao", "PAPA": "Papa Bear", "PAPADOGE": "Papa Doge", + "PAPAT": "PAPA Trump", "PAPER": "Dope Wars Paper", + "PAPERBASE": "Paper", "PAPI": "Papi", + "PAPO": "PAPO NINJA", "PAPPAY": "PAPPAY", "PAPU": "Papu Token", "PAPUSHA": "Papusha", "PAR": "Parachute", "PARA": "Paralink Network", "PARAB": "Parabolic", + "PARAD": "Paradox", "PARADOX": "The Paradox Metaverse", "PARAG": "Paragon Network", "PARAL": "Parallel", @@ -9075,6 +10411,7 @@ "PARLAY": "Parlay", "PARMA": "PARMA Fan Token", "PARQ": "PARQ", + "PARRY": "Parry Parrot", "PART": "Particl", "PAS": "Passive Coin", "PASC": "Pascal Coin", @@ -9085,13 +10422,17 @@ "PATEK": "Silly Patek", "PATEX": "Patex", "PATH": "PathDAO", + "PATRIOT": "Patriot", "PATTON": "Patton", + "PAUL": "Elephant Penguin", "PAVIA": "Pavia", "PAVO": "Pavocoin", "PAW": "PAWSWAP", + "PAWPAW": "PawPaw", "PAWS": "PawStars", "PAWSTA": "dogeatingpasta", "PAWTH": "Pawthereum", + "PAXE": "Paxe", "PAXEX": "PAXEX", "PAXG": "PAX Gold", "PAXU": "Pax Unitas", @@ -9099,6 +10440,7 @@ "PAY": "TenX", "PAYB": "Paybswap", "PAYCON": "Paycon", + "PAYD": "PAYD", "PAYN": "PayNet Coin", "PAYP": "PayPeer", "PAYS": "Payslink", @@ -9108,6 +10450,7 @@ "PAZZI": "Paparazzi", "PBAR": "Pangolin Hedera", "PBASE": "Polkabase", + "PBB": "PEPE BUT BLUE", "PBC": "PabyosiCoin", "PBET": "PBET", "PBIRB": "Parrotly", @@ -9139,41 +10482,55 @@ "PCR": "Paycer Protocol", "PCS": "Pabyosi Coin", "PCSP": "GenomicDao G-Stroke", + "PCW": "Power Crypto World", "PCX": "ChainX", "PD": "PUDEL", "PDA": "PlayDapp", + "PDAI": "Dai (Polygon Portal)", "PDATA": "PDATA", "PDC": "Project Decorum", "PDD": "PDDOLLAR", "PDEX": "Polkadex", "PDF": "Port of DeFi Network", + "PDJT": "President Donald J. Trump", "PDOG": "Polkadog", "PDOGE": "PolkaDoge", "PDRAGON": "Phoenix Dragon", "PDRIP": "Pulse Drip", "PDT": "ParagonsDAO", "PDX": "PDX Coin", + "PE": "Pe", "PEA": "Pea Farm", "PEACH": "Based Peaches", "PEACHY": "Peachy", "PEAK": "PEAKDEFI", + "PEAN": "Peanut the Squirrel (peanut-token.xyz)", "PEANIE": "Peanie", + "PEANU": "PEANUT INU", + "PEANUT": "#1 Tiktok Squirrel", + "PEAQ": "peaq", "PEAR": "Pear Swap", "PEARL": "Pearl Finance", "PEAS": "Peapods Finance", + "PEBIRD": "PEPE BIRD", "PEC": "PeaceCoin", "PECL": "PECland", "PED": "PEDRO", "PEDRO": "Pedro The Raccoon", "PEEL": "Meta Apes", + "PEENO": "Peeno", "PEEP": "Peepo", "PEEPA": "Peepa", "PEEPEE": "Peepee", "PEEPO": "PEEPO", + "PEEPOBASE": "Peepo (peepobase.org)", "PEEPS": "The People’s Coin", "PEEZY": "Young Peezy AKA Pepe", "PEFI": "Penguin Finance", "PEG": "PegNet", + "PEGA": "PEGA", + "PEGAMAGA": "Pepe Maga", + "PEGG": "PokPok Golden Egg", "PEGS": "PegShares", "PEIPEI": "PEIPEI", "PEKA": "PEKA", @@ -9183,22 +10540,29 @@ "PEL": "Propel Token", "PELF": "PELFORT", "PEM": "Pembrock", + "PEME": "PEME", "PENC": "PenCoin", "PENDLE": "Pendle", + "PENDY": "Pendy", "PENG": "Peng", "PENGCOIN": "PENG", + "PENGU": "Penguiana", "PENGYX": "PengyX", "PENIS": "PenisGrow", + "PENJ": "Penjamin Blinkerton", "PENP": "Penpie", "PENR": "Penrose Finance", "PENTA": "Penta", + "PEON": "Peon", "PEOPLE": "ConstitutionDAO", "PEOSONE": "pEOS", "PEP": "Pepechain", "PEPA": "Pepa Inu", + "PEPAY": "PEPAY", "PEPC": "Pepe Classic", "PEPE": "Pepe", "PEPE2": "Pepe 2.0", + "PEPE2024": "Olympic Pepe 2024", "PEPE20V1": "Pepe 2.0 v1", "PEPEA": "Pepeandybrettlandwolf", "PEPEAI": "Pepe Analytics", @@ -9206,12 +10570,14 @@ "PEPEB": "PEPEBOMB", "PEPEBNB": "Pepe The Frog", "PEPEBRC": "PEPE (Ordinals)", + "PEPEBSC": "Pepe Coin", "PEPEBURN": "Pepeburn", "PEPEC": "Pepe Chain", "PEPECASH": "Pepe Cash", "PEPECAT": "PEPE CAT", "PEPECEO": "REAL PEPE CEO", "PEPECHAIN": "PEPE Chain", + "PEPECO": "PEPE COIN BSC", "PEPECOIN": "PepeCoin", "PEPED": "PepeDAO Coin", "PEPEDAO": "PEPE DAO", @@ -9227,17 +10593,21 @@ "PEPEGRINCH": "Pepe Grinch", "PEPEINU": "PEPE inu", "PEPEKING": "PEPEKING", + "PEPEKRC20": "PEPE KRC20", "PEPELON": "Pepelon", "PEPEMAGA": "Trump Pepe", "PEPEMO": "PepeMo", "PEPEMOON": "PEPEMOON", + "PEPEOFSOL": "Pepe of Solana", "PEPEPI": "PEPEPi", "PEPER": "Baby Pepe", "PEPERA": "PEPERA", "PEPESOL": "PEPE SOL", + "PEPESOLCTO": "Pepe (pepesolcto.vip)", "PEPESORA": "Pepe Sora AI", "PEPESWAP": "PEPE Swap", "PEPET": "PepeTrump", + "PEPETR": "PEPE TREMP", "PEPEW": "PEPEPOW", "PEPEWIFHAT": "Pepewifhat", "PEPEWO": "PEPE World", @@ -9268,7 +10638,10 @@ "PESA": "Credible", "PESHI": "PESHI", "PESOBIT": "PesoBit", + "PESTO": "Pesto the Baby King Penguin", "PET": "Hello Pets", + "PETE": "PETE", + "PETERTODD": "Peter Todd", "PETF": "PEPE ETF", "PETG": "Pet Games", "PETH": "pETH", @@ -9293,7 +10666,7 @@ "PGEN": "Polygen", "PGF7T": "PGF500", "PGL": "Prospectors", - "PGN": "Pigeoncoin", + "PGN": "Paragon", "PGOLD": " Polkagold", "PGPT": "PrivateAI", "PGROK": "Papa Grok", @@ -9310,37 +10683,48 @@ "PHBD": "Polygon HBD", "PHC": "Profit Hunters Coin", "PHCR": "PhotoChromic", + "PHEN": "Phenx", + "PHEX": "HEX (Polygon Portal)", "PHI": "PHI Token", "PHIBA": "Papa Shiba", "PHIGOLD": "PhiGold Coin", + "PHIL": "Philtoken", "PHL": "Philcoin", "PHM": "Phomeum", + "PHMN": "POSTHUMAN", "PHN": "Phayny", "PHNX": "PhoenixDAO", "PHO": "Photon", + "PHOENIX": "Phoenix Finance", "PHONON": "Phonon DAO ", "PHOON": "Typhoon Cash", "PHORE": "Phore", "PHR": "Phreak", + "PHRYG": "PHRYGES", "PHRYGE": "PHRYGES", + "PHRYGES": "The Phryges", "PHS": "PhilosophersStone", "PHT": "Photon Token", "PHTC": "Photochain", "PHTR": "Phuture", + "PHUN": "PHUNWARE", "PHV": "PATHHIVE", - "PHX": "Phoenix Finance", "PI": "Plian", "PIA": "Olympia AI", "PIAS": "PIAS", "PIB": "Pibble", "PICA": "PicaArtMoney", + "PICKL": "PICKLE", "PICKLE": "Pickle Finance", "PICO": "PicoGo", + "PICOLO": "PICOLO", "PIE": "Persistent Information Exchange", + "PIERRE": "sacré bleu", "PIF": "Pepe Wif Hat", "PIG": "Pig Finance", "PIGE": "Pige", "PIGEON": "Pigeon In Yellow Boots", + "PIGEONC": "Pigeoncoin", "PIGGY": "Piggy", "PIGGYCOIN": "Piggy Coin", "PIGONK": "PIGONK", @@ -9348,18 +10732,24 @@ "PIIN": "piin (Ordinals)", "PIKA": "Pikaboss", "PIKACHU": "Pikachu Inu", + "PIKACRYPTO": "Pika", + "PIKAM": "Pikamoon", + "PIKE": "Pike Token", "PIKO": "Pinnako", "PILOT": "Unipilot", + "PIM": "PIM", "PIN": "Pin", "PINCHI": "Da Pinchi", "PINE": "Pine", "PINETWORKDEFI": "Pi Network DeFi", "PING": "CryptoPing", - "PINK": "PinkCoin", + "PINK": "PINK - The Panther", + "PINKCOIN": "PinkCoin", "PINKSALE": "PinkSale", "PINKX": "PantherCoin", "PINMO": "Pinmo", "PINO": "Pinocchu", + "PINS": "PINs Network Token", "PINU": "Piccolo Inu", "PINU100X": "Pi INU 100x", "PIO": "Pioneershares", @@ -9367,9 +10757,13 @@ "PIPA": "Pipa Coin", "PIPI": "Pippi Finance", "PIPL": "PiplCoin", + "PIPO": "Pipo", + "PIPPIN": "pippin", "PIPT": "Power Index Pool Token", "PIRATE": "Pirate Nation", "PIRATECASH": "PirateCash", + "PIRATECASHV1": "PirateCash v1", + "PIRATECASHV2": "PirateCash v2 (PirateCash Telegram bot)", "PIRATECOIN": "Pirate Coin Games", "PIRB": "PIRB", "PIRI": "Pirichain", @@ -9383,6 +10777,8 @@ "PIX": "Lampix", "PIXEL": "Pixels", "PIXELV": "PixelVerse", + "PIXFI": "Pixelverse", + "PIXL": "PIXL", "PIZA": "Half Pizza", "PIZPEPE": "Pepe Pizzeria", "PIZZA": "PizzaSwap", @@ -9411,7 +10807,7 @@ "PLATC": "PlatinCoin", "PLATINUM": "Platinum", "PLATO": "Plato Game", - "PLAY": "HEROcoin", + "PLAY": "Play Token", "PLAYC": "PlayChip", "PLAYCOIN": "PlayCoin", "PLAYKEY": "Playkey", @@ -9422,7 +10818,9 @@ "PLCUC": "PLC Ultima Classic", "PLD": "Plutonian DAO", "PLE": "Plethori", + "PLEA": "Plearn", "PLEB": "PLEBToken", + "PLEBONBASE": "PLEB", "PLENTY": "Plenty DeFi", "PLEO": "Empleos", "PLERF": "Plerf", @@ -9431,6 +10829,7 @@ "PLG": "Pledgecamp", "PLGR": "Pledge Finance", "PLI": "Plugin", + "PLINK": "Chainlink (Polygon Portal)", "PLM": "Plasmonics", "PLMC": "Polimec", "PLMT": "Pallium", @@ -9460,6 +10859,7 @@ "PLX": "PlexCoin", "PLXY": "Plxyer", "PLY": "Aurigami", + "PLYR": "PLYR L1", "PLZ": "PLUNZ", "PMA": "PumaPay", "PMD": "Pandemic Multiverse", @@ -9467,6 +10867,7 @@ "PMEER": "Qitmeer", "PMG": "Pomerium Ecosystem Token", "PMGT": "Perth Mint Gold Token", + "PMKR": "Maker (Polygon Portal)", "PMM": "Perpetual Motion Machine", "PMNT": "Paymon", "PMON": "Polkamon", @@ -9488,6 +10889,10 @@ "PNL": "True PNL", "PNODE": "Pinknode", "PNT": "pNetwork Token", + "PNUT": "Peanut the Squirrel", + "PNUTDOGE": "PNUT DOGE", + "PNUTRUMP": "Peanut Trump", + "PNUTS": "Pnuts for squirrel", "PNX": "PhantomX", "PNY": "Peony Coin", "POA": "Poa Network", @@ -9495,13 +10900,15 @@ "POC": "POC Blockchain", "POCAT": "Polite Cat", "POCC": "POC Chain", + "POCHITA": "Pochita", "POCKET": "XPocket", "POCO": "Pocoland", "POD": "Podo Point", "PODFAST": "PodFast", "PODIUM": "Smart League", "PODO": "Power Of Deep Ocean", - "POE": "Po.et", + "POE": "Portal Network", + "POET": "Po.et", "POG": "PolygonumOnline", "POGAI": "POGAI", "POGS": "POG", @@ -9528,6 +10935,7 @@ "POLKER": "Polker", "POLL": "Pollchain", "POLLUK": "Jasse Polluk", + "POLLUX": "Pollux Coin", "POLNX": "eToro Polish Zloty", "POLO": "NftyPlay", "POLS": "Polkastarter", @@ -9543,31 +10951,42 @@ "PONCHO": "Poncho", "POND": "Marlin", "PONGO": "Pongo", + "PONK": "PONK", "PONKE": "Ponke", "PONKEBNB": "Ponke BNB", + "PONKEI": "Chinese Ponkei the Original", "PONYO": "Ponyo Impact", "PONZI": "Ponzi", + "PONZIO": "Ponzio The Cat", "PONZU": "Ponzu Inu", "POO": "POOMOON", + "POOC": "Poo Chi", "POOCOIN": "PooCoin", "POODL": "Poodl", "POODOGE": "Poo Doge", "POOH": "POOH", + "POOKU": "Pooku", "POOL": "PoolTogether", "POOLX": "Poolz Finance", "POOLXT": "Pool-X", "POOLZ": "Poolz Finance", "POOP": "Poopsicle", + "POOPC": "Poopcoin", "POOWEL": "Joram Poowel", - "POP": "PopularCoin", - "POP!": "POP", + "POP": "Popcoin", "POPC": "PopChest", "POPCAT": "Popcat", + "POPCO": "Popcorn", "POPDOG": "PopDog", - "POPE": "Popecoin", + "POPE": "PopPepe", + "POPECOIN": "Popecoin", + "POPEPE": "POPEPE", + "POPGOAT": "Goatseus Poppimus", "POPK": "POPKON", "POPO": "popo", + "POPOETH": "POPO", "POPSICLE": "Popsicle Finance", + "POPULARCOIN": "PopularCoin", "POR": "Portugal National Team Fan Token", "PORA": "PORA AI", "PORK": "PepeFork", @@ -9594,11 +11013,14 @@ "POTS": "Moonpot", "POTTER": "POTTER", "POU": "Pou", + "POUPE": "Poupe", + "POUW": "Pouwifhat", "POW": "PowBlocks", "POWELL": "Jerome Powell", "POWER": "UniPower", "POWR": "Power Ledger", "POWSCHE": "Powsche", + "POX": "Monkey Pox", "PP": "ProducePay Chain", "PPAD": "PlayPad", "PPALPHA": "Phoenix Protocol", @@ -9630,6 +11052,7 @@ "PRDX": "ParamountDax Token", "PRE": "Presearch", "PREAI": "Predict Crypto", + "PREC": "Precipitate.AI", "PRED": "Predictcoin", "PREM": "Premium", "PREME": "PREME Token", @@ -9649,6 +11072,7 @@ "PRIME": "Echelon Prime", "PRIMECHAIN": "PrimeChain", "PRIMEETH": "Prime Staked ETH", + "PRIN": "Print The Pepe", "PRINT": "Printer.Finance", "PRINTERIUM": "Printerium", "PRINTS": "FingerprintsDAO", @@ -9664,6 +11088,8 @@ "PROC": "ProCurrency", "PROD": "Productivist", "PROGE": "Protector Roge", + "PROJECT89": "Project89", + "PROLIFIC": "Prolific Game Studio", "PROM": "Prometeus", "PROOF": "PROVER", "PROP": "Propeller", @@ -9673,6 +11099,7 @@ "PROPS": "Propbase", "PROPSPROJECT": "Props", "PROS": "Prosper", + "PROSP": "Prospective", "PROT": "PROT", "PROTEO": "Proteo DeFi", "PROTO": "Protocon", @@ -9684,6 +11111,7 @@ "PRPS": "Purpose", "PRPT": "Purple Token", "PRQ": "PARSIQ", + "PRRR": "Cats Are Liquidity", "PRS": "PressOne", "PRT": "Parrot Protocol", "PRTC": "Protectorate Protocol", @@ -9694,6 +11122,7 @@ "PRVS": "Previse", "PRX": "Parex", "PRXY": "Proxy", + "PRXYV1": "Proxy v1", "PRY": "PRIMARY", "PRZS": "Perezoso", "PS1": "POLYSPORTS", @@ -9717,6 +11146,7 @@ "PSTAKE": "pSTAKE Finance", "PSTN": "Piston", "PSUB": "Payment Swap Utility Board", + "PSUSHI": "Sushi (Polygon Portal)", "PSWAP": "Polkaswap", "PSY": "PsyOptions", "PSYOP": "PSYOP", @@ -9741,40 +11171,59 @@ "PTU": "Pintu Token", "PTX": "PlatinX", "PUCA": "Puss Cat", + "PUCCA": "PUCCA", "PUFETH": "pufETH", - "PUFF": "Puff", + "PUFF": "Puff The Dragon", + "PUFFCOIN": "Puff", + "PUFFER": "Puffer", "PUFFIN": "Puffin Global", - "PUFFT": "Puff The Dragon", + "PUFFV1": "Puff The Dragon v1", + "PUFFY": "Puffy", "PUGAI": "PUG AI", + "PUGDOG": "PUGDOG", + "PUGGY": "PUGGY Coin", "PUGL": "PugLife", + "PUGWIF": "PUGWIFHAT", + "PUL": "PulseTrailerPark", "PULI": "Puli", "PULSE": "Pulse", "PUMA": "Puma", "PUMBAA": "Pumbaa", "PUMLX": "PUMLx", "PUMP": "PUMP", + "PUMPBTC": "pumpBTC", + "PUMPFUNBAN": "Pump Fun Ban", "PUN": "Punkko", "PUNCH": "PUNCHWORD", "PUNDIX": "Pundi X", "PUNDU": "Pundu", + "PUNGU": "PUNGU", + "PUNI": "Uniswap Protocol Token (Polygon Portal)", "PUNK": "PunkCity", "PUNKAI": "PunkAI", "PUNKV": "Punk Vault (NFTX)", "PUP": "Puppy Coin", "PUPA": "PupaCoin", "PUPPER": "Pupper", + "PUPPET": "Puppet", + "PUPPETH": "Puppeth", "PUPPETS": "Puppets Coin", "PUPPIES": "I love puppies", "PUPS": "PUPS (Ordinals)", + "PUPU": "Pepe's Dog", "PURA": "Pura", "PURE": "Puriever", "PUREALT": "Pure", - "PURR": "SpartaCats", + "PURPE": "Purple Pepe", + "PURR": "Purr", + "PURRC": "Purrcoin", "PURSE": "Pundi X PURSE", "PUS": "Pussy Cat", "PUSD": "PegsUSD", + "PUSDC": "USD Coin (Polygon Portal)", "PUSH": "Ethereum Push Notification Service", "PUSHI": "Pushi", + "PUSS": "PussFi", "PUSSY": "Pussy Financial", "PUSSYINBIO": "Pussy In Bio", "PUT": "PutinCoin", @@ -9790,7 +11239,8 @@ "PWH": "pepewifhat", "PWINGS": "JetSwap pWings", "PWON": "Personal Wager", - "PWR": "PWR Coin", + "PWR": "MaxxChain", + "PWRC": "PWR Coin", "PWT": "PANDAINU", "PX": "PXcoin", "PXB": "PixelBit", @@ -9819,6 +11269,7 @@ "PYT": "Payther", "PYTH": "Pyth Network", "PYUSD": "PayPal USD", + "PZETH": "pzETH", "PZM": "Prizm", "PZP": "PlayZap", "PZT": "Pizon", @@ -9850,6 +11301,8 @@ "QDT": "QCHAIN", "QDX": "Quidax", "QFI": "QFinance", + "QGOLD": "Quorium", + "QGOV": "Q Protocol", "QI": "BENQI", "QIE": "QI Blockchain", "QINGWA": "ShangXin QingWa", @@ -9884,16 +11337,21 @@ "QSR": "Quasar", "QSWAP": "Quantum Network", "QTC": "Qitcoin", + "QTCC": "Quick Transfer coin", "QTCON": "Quiztok", + "QTDAO": "Quantum DAO", "QTF": "Quantfury", "QTK": "QuantCheck", "QTL": "Quatloo", + "QTLX": "Quantlytica", "QTO": "QToken", "QTUM": "QTUM", "QTZ": "Quartz", "QUA": "Quantum Tech", + "QUAC": "QUACK", "QUACK": "Rich Quack", "QUAM": "Quam Network", + "QUAN": "Quant AI", "QUANT": "Quant Finance", "QUARASHI": "Quarashi Network", "QUARK": "Quark", @@ -9903,6 +11361,8 @@ "QUBE": "Qube", "QUBIC": "Qubic", "QUBITICA": "Qubitica", + "QUBY": "Quby", + "QUE": "Queen Of Memes", "QUEEN": "Queen of Engrand", "QUICK": "Quickswap", "QUICKOLD": "Quickswap", @@ -9931,6 +11391,7 @@ "R4RE": "R4RE Token", "RAB": "Rabbit", "RABB": "Rabbit INU", + "RABBI": "Len \"rabbi\" Sassaman", "RABBIT": "Rabbit Finance", "RABI": "Rabi", "RAC": "RAcoin", @@ -9947,6 +11408,7 @@ "RAFFLES": "Degen Raffles", "RAFL": "RAFL", "RAFT": "Raft", + "RAGDOLL": "Ragdoll", "RAGE": "Rage Fan", "RAI": "Rai Reflex Index", "RAID": "Raid Token", @@ -9956,6 +11418,7 @@ "RAIN": "Rainmaker Games", "RAINBOW": "Rainbow Token", "RAINC": "RainCheck", + "RAINCO": "Rain Coin", "RAINI": "Rainicorn", "RAISE": "Raise Token", "RAIT": "Rabbitgame", @@ -9972,6 +11435,7 @@ "RAP": "Philosoraptor", "RAPDOGE": "RapDoge", "RAPTOR": "Jesus-Raptor", + "RAR": "Rare Pepe", "RARE": "SuperRare", "RARI": "Rarible", "RASTA": "ZionLabs Token", @@ -10011,6 +11475,7 @@ "RBUNNY": "Rocket Bunny", "RBW": "Crypto Unicorns Rainbow", "RBX": "RabbitX", + "RBXDEFI": "RBX", "RBXS": "RBXSamurai", "RBY": "RubyCoin", "RC": "Russiacoin", @@ -10022,7 +11487,8 @@ "RCKT": "RocketSwap", "RCM": "READ2N", "RCN": "Ripio", - "RCOIN": "RCoin", + "RCOIN": "ArCoin", + "RCOINEU": "RCoin", "RCT": "RealChain", "RCX": "RedCrowCoin", "RD": "Round Dollar", @@ -10036,6 +11502,7 @@ "RDNT": "Radiant Capital", "RDNTV1": "Radiant Capital v1", "RDO": "Rodeo Finance", + "RDOG": "Repost Dog", "RDPX": "Dopex Rebate Token", "RDR": "Rise of Defenders", "RDS": "Reger Diamond", @@ -10046,17 +11513,22 @@ "REAL": "RealLink", "REALM": "Realm", "REALMS": "Realms of Ethernity", + "REALP": "Real Pepe", "REALPLATFORM": "REAL", + "REALTRACT": "RealTract", "REALY": "Realy Metaverse", "REAP": "ReapChain", "REAPER": "Grim Finance", "REAU": "Vira-lata Finance", + "REBD": "REBORN", "REBL": "REBL", "REBUS": "Rebuschain", "REC": "Rec Token (REC)", "RECA": "The Resistance Cat", "RECKOON": "Reckoon", "RECOM": "Recom", + "RECORD": "Music Protocol", + "RECT": "ReflectionAI", "RED": "RED TOKEN", "REDC": "RedCab", "REDCO": "Redcoin", @@ -10071,6 +11543,7 @@ "REDO": "Resistance Dog", "REDP": "Red Ponzi Gud", "REDPEPE": "Red Pepe", + "REDTH": "Red The Mal", "REDZILLA": "REDZILLA COIN", "REE": "ReeCoin", "REEE": "REEE", @@ -10085,6 +11558,7 @@ "REGALCOIN": "Regalcoin", "REGEN": "Regen Network", "REGENT": "REGENT COIN", + "REGI": "Resistance Girl", "REHA": "Resistance Hamster", "REHAB": "NFT Rehab", "REI": "REI Network", @@ -10100,28 +11574,34 @@ "REM": "REMME", "REMCO": "Remco", "REME": "REME-Coin", + "REMILIA": " Remilia", "REMIT": "BlockRemit", "REN": "REN", "RENA": "Warena", "RENBTC": "renBTC", "RENC": "RENC", + "RENDER": "Render Network", "RENDOGE": "renDOGE", - "RENE": "Renewable Energy", "RENEC": "RENEC", "RENQ": "Renq Finance", "RENS": "Rens", "RENT": "Rent AI", "RENTBE": "Rentberry", "REP": "Augur", + "REPE": "Resistance Pepe", "REPO": "Repo Coin", + "REPUB": "Republican", + "REPUBLICAN": "Republican", "REPUX": "Repux", + "REPV1": "Reputation", "REQ": "Request Network", "RES": "Resistance", "RESCUE": "Rescue", "REST": "Restore", - "RET": "RealTract", + "RET": "Renewable Energy", "RETA": "Realital Metaverse", "RETAIL": "Retail.Global", + "RETARDIA": "RETARDIA", "RETARDIO": "RETARDIO", "RETH": "Rocket Pool ETH", "RETH2": "rETH2", @@ -10132,17 +11612,22 @@ "REV": "Revain", "REV3L": "REV3AL", "REVA": "Revault Network", + "REVAL": "RevaLink Wallet Token", "REVE": "Revenu", "REVO": "Revomon", "REVOAI": "revoAI", + "REVOL": "Revolution", "REVOLAND": "Revoland Governance Token", "REVON": "RevoNetwork", "REVU": "Revuto", "REVV": "REVV", "REW": "Review.Network", + "REWARD": "Rewardable", "REX": "Imbrex", + "REXHAT": "rexwifhat", "REZ": "Renzo", "RF": "Raido Financial", + "RFC": "Royal Finance Coin", "RFCTR": "Reflector.Finance", "RFD": "RefundCoin", "RFDB": "Refund", @@ -10152,6 +11637,7 @@ "RFL": "RAFL", "RFOX": "RedFOX Labs", "RFR": "Refereum", + "RFRM": "Reform DAO", "RFT": "Rangers Fan Token", "RFUEL": "Rio DeFi", "RFX": "Reflex", @@ -10169,12 +11655,14 @@ "RHP": "Rhypton Club", "RIA": "aRIA Currency", "RIB": "Ribus", + "RIBB": "Ribbit", "RIBBIT": "Ribbit", "RIC": "Riecoin", "RICE": "RiceFarm", "RICECOIN": "RiceCoin", "RICH": "Richie", "RICHOFME": "Rich Of Memes", + "RICHR": "RichRabbit", "RICK": "Infinite Ricks", "RICKMORTY": "Rick And Morty", "RIDE": "Holoride", @@ -10189,16 +11677,23 @@ "RIMBIT": "Rimbit", "RIN": "Aldrin", "RING": "Darwinia Network", + "RINGA": "Ring AI", + "RINGF": "Ring Financial", "RINGX": "RING X PLATFORM", "RINIA": "Rinia Inu", + "RINO": "Rino", + "RINT": "Rin Tin Tin", + "RINTARO": "Rintaro", "RINU": "Raichu Inu", "RIO": "Realio Network", "RIOT": "Riot Racers", + "RIOV1": "Realio Network v1", "RIP": "Fantom Doge", "RIPAX": "RipaEx", "RIPO": "RipOffCoin", "RIPT": "RiptideCoin", "RIPTO": "RiptoBuX", + "RIS": "Riser", "RISE": "EverRise", "RISEP": "Rise Protocol", "RISEVISION": "Rise", @@ -10209,7 +11704,9 @@ "RIVUS": "RivusDAO", "RIYA": "Etheriya", "RIZE": "Rizespor Token", - "RIZO": "Rizo", + "RIZO": "HahaYes", + "RIZOLOL": "Rizo", + "RIZZ": "Rizz", "RJV": "Rejuve.AI", "RKC": "Royal Kingdom Coin", "RKI": "RAKHI", @@ -10221,6 +11718,7 @@ "RLM": "MarbleVerse", "RLOOP": "rLoop", "RLT": "Runner Land", + "RLUSD": "Ripple USD", "RLX": "Relex", "RLY": "Rally", "RMATIC": "StaFi Staked MATIC", @@ -10240,6 +11738,7 @@ "RND": "The RandomDAO", "RNDR": "Render Token", "RNDX": "Round X", + "RNEAR": "Near (Rainbow Bridge)", "RNS": "RenosCoin", "RNT": "OneRoot Network", "RNTB": "BitRent", @@ -10252,6 +11751,8 @@ "ROBIN": "Robin of Da Hood", "ROBINH": "ROBIN HOOD", "ROBO": "RoboHero", + "ROBOTA": "TAXI", + "ROBOTAXI": "ROBOTAXI", "ROC": "Rasputin Online Coin", "ROCCO": "Just A Rock", "ROCK": "Bedrock", @@ -10261,10 +11762,12 @@ "ROCKETFI": "RocketFi", "ROCKI": "Rocki", "ROCKY": "Rocky", + "ROCKYCOIN": "ROCKY", "ROCO": "ROCO FINANCE", "RODAI": "ROD.AI", "ROE": "Rover Coin", "ROG": "ROGin AI", + "ROGER": "ROGER", "ROI": "ROIcoin", "ROK": "Rockchain", "ROKM": "Rocket Ma", @@ -10289,7 +11792,10 @@ "ROS": "ROS Coin", "ROSA": "Rosa Inu", "ROSE": "Oasis Labs", + "ROSEC": "Rosecoin", + "ROSEW": "RoseWifHat", "ROSN": "Roseon Finance", + "ROSS": "Ross Ulbricht", "ROSX": "Roseon", "ROT": "Rotten", "ROTTY": "ROTTYCOIN", @@ -10297,6 +11803,7 @@ "ROUP": "Roup (Ordinals)", "ROUSH": "Roush Fenway Racing Fan Token", "ROUTE": "Router Protocol", + "ROUTEV1": "Router Protocol v1", "ROVI": "ROVI", "ROW": "Rage On Wheels", "ROWAN": "Sifchain", @@ -10310,6 +11817,7 @@ "RPD": "Rapids", "RPEPEc": "RoaringPepe", "RPG": "Rangers Protocol", + "RPGV1": "Rangers Protocol v1", "RPILL": "Red Pill", "RPK": "RepubliK", "RPL": "RocketPool", @@ -10320,9 +11828,11 @@ "RPT": "Rug Proof", "RPTR": "Raptor Finance", "RPUT": "Robin8 Profile Utility Token", + "RPX": "Red Pulse Token", "RPZX": "Rapidz", "RRB": "Renrenbit", "RRC": "Recycling Regeneration Chain", + "RREN": "REN (Rainbow Bridge)", "RRT": "Recovery Right Tokens", "RS": "ReadySwap", "RSC": "ResearchCoin", @@ -10339,18 +11849,22 @@ "RST": "REGA Risk Sharing Token", "RSTK": "Restake Finance", "RSUN": "RisingSun", + "RSUSHI": "Sushi (Rainbow Bridge)", "RSV": "Reserve", "RSWETH": "Restaked Swell Ethereum", "RT2": "RotoCoin", "RTB": "AB-CHAIN", "RTC": "Reltime", + "RTD": "Retard", "RTE": "Rate3", "RTF": "Ready to Fight", "RTH": "Rotharium", "RTK": "RetaFi", "RTM": "Raptoreum", + "RTR": "Restore The Republic", "RTT": "Restore Truth Token", "RU": "RIFI United", + "RUBB": "Rubber Ducky Cult", "RUBCASH": "RUBCASH", "RUBIT": "Rublebit", "RUBX": "eToro Russian Ruble", @@ -10360,17 +11874,21 @@ "RUFF": "Ruff", "RUG": "Rug", "RUGA": "RUGAME", + "RUGPULL": "Captain Rug Pull", "RUGZ": "pulltherug.finance", "RULER": "Ruler Protocol", "RUM": "RUM Pirates of The Arrland Token", "RUN": "Run", "RUNE": "Thorchain", + "RUNEVM": "RUNEVM", + "RUNNER": "Runner", "RUNY": "Runy", "RUP": "Rupee", "RUPX": "Rupaya", "RUSD": "Reflecto USD", "RUSH": "RUSH COIN", "RUSHCMC": "RUSHCMC", + "RUSSELL": "Russell", "RUST": "RustCoin", "RUSTBITS": "Rustbits", "RUTH": "RUTH", @@ -10403,23 +11921,27 @@ "RXT": "RIMAUNANGIS", "RYC": "RoyalCoin", "RYCN": "RoyalCoin 2.0", + "RYD": "RYderOSHI", "RYIU": "RYI Unity", - "RYO": "Ryo", + "RYO": "RYO Coin", + "RYOCURRENCY": "Ryo", "RYOMA": "Ryoma", "RYOSHI": "Ryoshis Vision", "RYU": "The Blue Dragon", "RYZ": "Anryze", "RZR": "RazorCoin", "RedFlokiCEO": "Red Floki CEO", + "S": "Sonic Labs", "S2K": "Sports 2K75", "S315": "SWAP315", "S4F": "S4FE", "S8C": "S88 Coin", "SA": "Superalgos", - "SABAI": "Sabai Ecoverse", + "SABAI": "Sabai Protocol", "SABLE": "Sable Finance", "SABR": "SABR Coin", "SAC1": "Sable Coin", + "SAD": "SadCat", "SAF": "Safinus", "SAFE": "Safe", "SAFEBTC": "SafeBTC", @@ -10429,6 +11951,7 @@ "SAFEHAMSTERS": "SafeHamsters", "SAFELIGHT": "SafeLight", "SAFELUNAR": "SafeLunar", + "SAFEM": "SAFEMOON SOLANA", "SAFEMARS": "Safemars", "SAFEMOO": "SafeMoo", "SAFEMOON": "SafeMoon", @@ -10444,6 +11967,7 @@ "SAFUU": "SAFUU", "SAGA": "Saga", "SAGACOIN": "SagaCoin", + "SAGE": "Ceremonies AI", "SAI": "SAI", "SAIL": "SAIL", "SAITA": "SaitaChain", @@ -10452,6 +11976,7 @@ "SAITAMAV1": "Saitama v1", "SAITANOBI": "Saitanobi", "SAITO": "Saito", + "SAIY": "Saiyan PEPE", "SAK": "SharkCoin", "SAKAI": "Sakai Vault", "SAKATA": "Sakata Inu", @@ -10459,20 +11984,24 @@ "SAL": "SalPay", "SALD": "Salad", "SALE": "DxSale Network", + "SALL": "Sallar", "SALLY": "SALAMANDER", "SALMAN": "Mohameme Bit Salman", "SALMON": "Salmon", "SALT": "Salt Lending", "SAM": "Samsunspor Fan Token", "SAMA": "Moonsama", + "SAMMY": "Samoyed", "SAMO": "Samoyedcoin", "SAN": "Santiment", "SANA": "Storage Area Network Anywhere", + "SANCHO": "Sancho", "SAND": "The Sandbox", "SANDG": "Save and Gain", "SANDWICH": " Sandwich Network", "SANDY": "Sandy", "SANI": "Sanin Inu", + "SANIN": "Sanin", "SANJI": "Sanji Inu", "SANSHU": "Sanshu Inu", "SANTA": "SANTA CHRISTMAS INU", @@ -10487,10 +12016,14 @@ "SARCO": "Sarcophagus", "SAROS": "Saros", "SAS": "Stand Share", + "SASHA": "SASHA CAT", "SASHIMI": "Sashimi", "SAT": "Satisfaction Token", "SAT2": "Saturn2Coin", "SATA": "Signata", + "SATAN": "MrBeast's Cat", + "SATO": "Atsuko Sato", + "SATORI": "Satori Network", "SATOSHINAKAMOTO": "Satoshi Nakamoto", "SATOX": "Satoxcoin", "SATOZ": "Satozhi", @@ -10505,21 +12038,25 @@ "SAUDIPEPE": "SAUDI PEPE", "SAUDISHIB": "Saudi Shiba Inu", "SAUNA": "SaunaFinance Token", + "SAV": "Save America", "SAV3": "SAV3", "SAVG": "SAVAGE", "SAVM": "SatoshiVM", + "SAY": "SAY Coin", "SB": "DragonSB", "SBA": "simplyBrand", "SBABE": "SNOOPYBABE", "SBAE": "Salt Bae For The People", "SBC": "StableCoin", "SBCC": "Smart Block Chain City", + "SBCH": "Smart Bitcoin Cash", "SBE": "Sombe", "SBEFE": "BEFE", "SBET": "SBET", "SBF": "SBF In Jail", "SBGO": "Bingo Share", "SBIO": "Vector Space Biosciences, Inc.", + "SBNB": "Binance Coin (SpookySwap)", "SBOX": "SUIBOXER", "SBR": "Saber", "SBRT": "SaveBritney", @@ -10537,17 +12074,21 @@ "SCAPE": "Etherscape", "SCAR": "Velhalla", "SCARAB": "Scarab Finance", + "SCARCITY": "SCARCITY", "SCASH": "SpaceCash", "SCAT": "Sad Cat Token", "SCC": "StockChain Coin", "SCCP": "S.C. Corinthians Fan Token", "SCDS": "Shrine Cloud Storage Network", + "SCF": "Smoking Chicken Fish", "SCFX": "Shui CFX", "SCH": "SoccerHub", "SCHO": "Scholarship Coin", "SCHR": "Schrodinger", + "SCHRO": "Schrodinger", "SCHRODI": "Schrödi", "SCIA": "Stem Cell", + "SCIHUB": "sci-hub", "SCIVIVE": "sciVive", "SCIX": "Scientix", "SCK": "Space Corsair Key", @@ -10565,6 +12106,7 @@ "SCOR": "Scorista", "SCORE": "Scorecoin", "SCOT": "Scotcoin", + "SCOTT": "Scottish", "SCOTTY": "Scotty Beam", "SCP": "ScPrime", "SCPT": "Script Network", @@ -10577,14 +12119,19 @@ "SCRIV": "SCRIV", "SCRL": "Scroll", "SCRM": "Scorum", + "SCROLL": "Scroll Network", + "SCROLLY": "Scrolly the map", "SCROOGE": "Scrooge", "SCRPT": "ScryptCoin", "SCRT": "Secret", + "SCRVUSD": "Savings crvUSD", "SCRYPTA": "Scrypta", + "SCRYPTTOKEN": "ScryptToken", "SCS": "Solcasino Token", "SCSX": "Secure Cash", - "SCT": "ScryptToken", + "SCT": "SuperCells", "SCTK": "SharesChain", + "SCUBA": "Scuba Dog", "SCY": "Synchrony", "SD": "Stader", "SDA": "SDChain", @@ -10594,6 +12141,7 @@ "SDCRV": "Stake DAO CRV", "SDEX": "SmarDex", "SDL": "Saddle Finance", + "SDME": "SDME", "SDN": "Shiden Network", "SDO": "TheSolanDAO", "SDOG": "Small Doge", @@ -10606,7 +12154,8 @@ "SDT": "TerraSDT", "SDUSD": "SDUSD", "SDX": "SwapDEX", - "SEA": "Second Exchange Alliance", + "SEAGULL": "SEAGULL SAM", + "SEAIO": "Second Exchange Alliance", "SEAL": "Seal Finance", "SEALN": "Seal Network", "SEAM": "Seamless Protocol", @@ -10632,26 +12181,37 @@ "SEG": "Solar Energy", "SEI": "Sei", "SEILOR": "Kryptonite", + "SEIYAN": "Seiyan Token", "SEKAI": "Sekai DAO", + "SEKOIA": "sekoia by Virtuals", "SEL": "SelenCoin", "SELF": "SELFCrypto", + "SELFI": "SelfieSteve", "SELFIE": "SelfieDogCoin", + "SELFIEC": "Selfie Cat", + "SELFT": "SelfToken", "SELLC": "Sell Token", "SEM": "Semux", "SEN": "Sentaro", "SENATE": "SENATE", "SENC": "Sentinel Chain", "SEND": "Social Send", + "SENDOR": "Sendor", "SENK": "Senk", "SENNO": "SENNO", "SENSE": "Sense Token", "SENSI": "Sensi", "SENSO": "SENSO", "SENSOR": "Sensor Protocol", + "SENSOV1": "SENSO v1", + "SENSUS": "Sensus", "SENT": "Sentinel", + "SENTI": "Sentinel Bot Ai", + "SENTR": "Sentre Protocol", "SEON": "Seedon", "SEOR": "SEOR Network", "SEOS": "Smart Eye Operating System", + "SEP": "Smart Energy Pay", "SEPA": "Secure Pad", "SEQ": "Sequence", "SER": "Secretum", @@ -10670,6 +12230,7 @@ "SEW": "simpson in a memes world", "SEX": "SEX Odyssey", "SEXY": "EthXY", + "SEXYP": "SEXY PEPE", "SFARM": "SolFarm", "SFC": "Solarflarecoin", "SFCP": "SF Capital", @@ -10682,9 +12243,11 @@ "SFIT": "Sense4FIT", "SFL": "Sunflower Land", "SFLOKI": "SuiFloki-Inu", + "SFLR": "Sceptre Staked FLR", "SFM": "SafeMoon V2", "SFP": "SafePal", "SFR": "SaffronCoin", + "SFRAX": "Staked FRAX", "SFRC": "Safari Crush", "SFRXETH": "Frax Staked Ether", "SFT": "SportsFix", @@ -10711,12 +12274,14 @@ "SHA": "Safe Haven", "SHACK": "Shackleford", "SHACOIN": "Shacoin", + "SHAD": "Shadowswap Finance", "SHADE": "ShadeCoin", "SHAK": "Shakita Inu", "SHAKE": "Spaceswap SHAKE", "SHAMAN": "Shaman King Inu", "SHAN": "Shanum", "SHANG": "Shanghai Inu", + "SHAR": "Shark Cat", "SHARBI": "SHARBI", "SHARD": "ShardCoin", "SHARDS": "SolChicks Shards", @@ -10725,9 +12290,12 @@ "SHARES": "shares.finance", "SHARK": "Sharky", "SHARKC": "Shark Cat", + "SHARKI": "Sharki", + "SHARP": "Sharp", "SHARPE": "Sharpe Capital", "SHAUN": "SHAUN INU", "SHB4": "Super Heavy Booster 4", + "SHC": "School Hack Coin", "SHD": "ShardingDAO", "SHDW": "Shadow Token", "SHE": "Shine Chain", @@ -10735,6 +12303,7 @@ "SHEEESH": "Secret Gem", "SHEESH": "Sheesh it is bussin bussin", "SHEESHA": "Sheesha Finance", + "SHEGEN": "Aiwithdaddyissues", "SHEI": "SheikhSolana", "SHELL": "Shell Token", "SHEN": "Shen", @@ -10754,6 +12323,7 @@ "SHIBAAI": "SHIBAAI", "SHIBAC": "SHIBA CLASSIC", "SHIBACASH": "ShibaCash", + "SHIBADOG": "Shiba San", "SHIBAI": "AiShiba", "SHIBAKEN": "Shibaken Finance", "SHIBAMOM": "Shiba Mom", @@ -10764,6 +12334,7 @@ "SHIBCAT": "SHIBCAT", "SHIBCEO": "ShibCEO", "SHIBDOGE": "ShibaDoge", + "SHIBEINU": "Shibe Inu", "SHIBELON": "ShibElon", "SHIBEMP": "Shiba Inu Empire", "SHIBGF": "Shiba Girlfriend", @@ -10775,6 +12346,8 @@ "SHIBLITE": "Shiba Lite", "SHIBMERICAN": "Shibmerican", "SHIBO": "ShiBonk", + "SHIBON": "SHIB ON SOLANA", + "SHIBS": "Shibsol", "SHIBTC": "Shibabitcoin", "SHIBU": "SHIBU INU", "SHICO": "ShibaCorgi", @@ -10784,6 +12357,7 @@ "SHIFT": "Shift", "SHIH": "Shih Tzu", "SHIK": "Shikoku", + "SHIKOKU": "Mikawa Inu", "SHIL": "Shila Inu", "SHILL": "SHILL Token", "SHILLD": "SHILLD", @@ -10792,10 +12366,14 @@ "SHINA": "Shina Inu", "SHINJA": "Shibnobi", "SHINO": "ShinobiVerse", + "SHINOB": "Shinobi", "SHINT": "Shiba Interstellar", "SHIP": "ShipChain", + "SHIR": "SHIRO", "SHIRYOINU": "Shiryo-Inu", + "SHISHA": "Shisha Coin", "SHIT": "I will poop it NFT", + "SHITC": "Shitcoin", "SHIV": "Shiva Inu", "SHK": "Shrike", "SHL": "Oyster Shell", @@ -10804,11 +12382,14 @@ "SHNT": "Sats Hunters", "SHO": "Showcase Token", "SHOE": "ShoeFy", + "SHOG": "SHOG", + "SHOGGOTH": "Shoggoth", "SHOKI": "Shoki", "SHON": "ShonToken", "SHOOT": "Mars Battle", "SHOOTER": "Top Down Survival Shooter", "SHOP": "Shoppi Coin", + "SHOPN": "ShopNEXT", "SHOPX": "Splyt", "SHORK": "shork", "SHORTY": "ShortyCoin", @@ -10820,10 +12401,12 @@ "SHRED": "ShredN", "SHREK": "ShrekCoin", "SHRIMP": "SHRIMP", + "SHROO": "Shroomates", "SHROOM": "Shroom.Finance", "SHROOMFOX": "Magic Shroom", "SHRUB": "Shrub", "SHS": "SHEESH", + "SHU": "Shutter", "SHUB": "SimpleHub", "SHUFFLE": "SHUFFLE!", "SHVR": "Shivers", @@ -10841,44 +12424,60 @@ "SIFT": "Smart Investment Fund Token", "SIFU": "SIFU", "SIG": "Signal", + "SIGMA": "SIGMA", "SIGN": "Sign Token", "SIGNA": "Signa", "SIGNAT": "SignatureChain", "SIGT": "Signatum", "SIGU": "Singular", + "SIKA": "SikaSwap", "SIL": "SIL Finance Token V2", "SILK": "SilkCoin", "SILKR": "SilkRoadCoin", "SILKT": "SilkChain", + "SILL": "Silly Duck", "SILLY": "Silly Dragon", "SILO": "Silo Finance", + "SILV": "Silver Surfer Solana", "SILV2": "Escrowed Illuvium 2", "SILVA": "Silva Token", "SILVER": "SILVER", + "SILVERKRC": "Silver KRC-20", + "SILVERSTAND": "Silver Standard", "SILVERWAY": "Silverway", + "SIM": "Simpson", "SIMP": "SO-COL", "SIMPLE": "SimpleChain", + "SIMPS": "Simpson MAGA", + "SIMPSO": "Simpson Neiro", "SIMPSON": "Homer", "SIMPSON6900": "Simpson6900 ", + "SIMPSONF": "Simpson FUKU", + "SIMPSONP": "Simpson Predictions", "SIMPSONSINU": "The Simpsons Inu", + "SIMPSONT": "Simpson Trump", "SIMSOL": "SimSol", "SIN": "Sinverse", "SINE": "Sinelock", "SING": "SingularFarm", "SINGLE": "Single Finance", + "SINK": "Let that sink in", "SINS": "SafeInsure", "SINSO": "SINSO", "SINX": "SINX Token", + "SIO": "SAINO", "SION": "FC Sion", "SIP": "Space SIP", "SIPHER": "Sipher", "SIPHON": "Siphon Life Spell", "SIR": "Sir", + "SIRIUS": "first reply", "SIS": "Symbiosis Finance", "SISA": "Strategic Investments in Significant Areas", "SISC": "Shirushi Coin", "SISHI": "Sishi Finance", "SIU": "Siu", + "SIUU": "SIUUU", "SIUUU": "Crustieno Renaldo", "SIV": "Sivasspor Token", "SIX": "SIX Network", @@ -10888,11 +12487,13 @@ "SJCX": "StorjCoin", "SKAI": "Skillful AI", "SKB": "SkullBuzz", + "SKBDI": "Skibidi Toilet", "SKC": "Skeincoin", "SKCS": "Staked KCS", "SKEB": "Skeb", "SKET": "Sketch coin", "SKEY": "SmartKey", + "SKG888": "Safu & Kek Gigafundz 888", "SKI": "Skillchain", "SKIBIDI": "Skibidi Toilet", "SKID": "Success Kid", @@ -10916,14 +12517,18 @@ "SKT": "Sukhavati Network", "SKU": "Sakura", "SKULL": "Pirate Blocks", - "SKY": "Skycoin", + "SKX": "SKPANAX", + "SKY": "Sky", "SKYA": "Sekuya Multiverse", + "SKYCOIN": "Skycoin", "SKYFT": "SKYFchain", "SKYM": "SkyMap", "SKYRIM": "Skyrim Finance", "SKYX": "SKUYX", "SLA": "SUPERLAUNCH", "SLAM": "Slam Token", + "SLAP": "CatSlap", + "SLAVI": "Slavi Coin", "SLB": "Solberg", "SLC": "Solice", "SLCL": "Solcial", @@ -10934,6 +12539,7 @@ "SLERF2": "SLERF 2.0", "SLERFFORK": "SlerfFork", "SLEX": "SLEX Token", + "SLF": "Self Chain", "SLG": "Land Of Conquest", "SLICE": "Tranche Finance", "SLICEC": "SLICE", @@ -10947,8 +12553,10 @@ "SLND": "Solend", "SLNV2": "SLNV2", "SLOKI": "Super Floki", + "SLOP": "Slop", "SLORK": "SLORK", "SLOTH": "Sloth", + "SLOTHA": "Slothana", "SLP": "Smooth Love Potion", "SLPV1": "Smooth Love Potion v1", "SLR": "SolarCoin", @@ -10957,6 +12565,7 @@ "SLS": "SaluS", "SLST": "SmartLands", "SLT": "Social Lending Network", + "SLUGDENG": "SLUG DENG", "SLUMBO": "SLUMBO", "SLVX": "eToro Silver", "SLX": "Slate", @@ -10967,6 +12576,7 @@ "SMART": "SmartCash", "SMARTB": "Smart Coin", "SMARTCREDIT": "SmartCredit Token", + "SMARTH": "SmartHub", "SMARTLOX": "SmartLOX", "SMARTM": "SmartMesh", "SMARTMEME": "SmartMEME", @@ -10981,15 +12591,18 @@ "SMCW": "Space Misfits", "SMD": "SMD Coin", "SMETA": "StarkMeta", + "SMETX": "SpecialMetalX", "SMF": "SmurfCoin", "SMG": "Smaugs NFT", "SMH": "Spacemesh", "SMI": "SafeMoon Inu", "SMIDGE": "Smidge", + "SMIDGEETH": "Smidge", "SMILE": "Smile Token", "SMILEK": "Smilek to the Bank", "SMILEY": "SMILEY", "SMILY": "Smily Trump", + "SMKNG": "SmonkeyKong", "SML": "Saltmarble", "SMLY": "SmileyCoin", "SMM": "TrendingTool.io", @@ -11018,15 +12631,19 @@ "SMX": "Snapmuse.io", "SN": "SpaceN", "SNA": "SUKUYANA", + "SNAC": "SnackboxAI", "SNACK": "Crypto Snack", "SNAIL": "SnailBrook", + "SNAKE": "snake", "SNAKES": "Snakes Game", "SNAP": "SnapEx", + "SNAPCAT": "Snapcat", "SNB": "SynchroBitcoin", "SNC": "SunContract", "SNCT": "SnakeCity", "SND": "Sandcoin", "SNE": "StrongNode", + "SNEED": "Sneed", "SNEK": "Snek", "SNEKE": "Snek on Ethereum", "SNET": "Snetwork", @@ -11034,6 +12651,9 @@ "SNFTS": "Seedify NFT Space", "SNG": "SINERGIA", "SNGLS": "SingularDTV", + "SNGT": "SNG Token", + "SNIBBU": "Snibbu The Crab", + "SNIFT": "StarryNift", "SNIP": "LyrnAI", "SNIPPEPE": "SNIPING PEPE", "SNITCH": "Randall", @@ -11043,6 +12663,7 @@ "SNMT": "Satoshi Nakamoto Token", "SNN": "SeChain", "SNOB": "Snowball", + "SNOLEX": "Snolex", "SNOOP": "SnoopDAO", "SNOOPY": "Snoopy", "SNORK": "Snork", @@ -11072,20 +12693,25 @@ "SOBA": "SOBA Token", "SOBB": "SoBit", "SOBER": "Solabrador", + "SOBULL": "SoBULL", "SOC": "All Sports Coin", "SOCA": "Socaverse", "SOCC": "SocialCoin", "SOCCER": "SoccerInu", + "SOCIAL": "Phavercoin", "SOCKS": "Unisocks", "SOCOLA": "SOCOLA INU", "SODA": "SODA Coin", + "SODAL": "Sodality Coin", "SODO": "Scooby Doo", + "SOFAC": "SofaCat", "SOFI": "RAI Finance", "SOFTCO": "SOFT COQ INU", "SOH": "Stohn Coin", "SOHOT": "SOHOTRN", "SOIL": "SoilCoin", "SOJ": "Sojourn Coin", + "SOK": "shoki", "SOKU": "Soku Swap", "SOL": "Solana", "SOL10": "SOLANA MEME TOKEN", @@ -11094,21 +12720,27 @@ "SOLALA": "Solala", "SOLAMA": "Solama", "SOLAMB": "SOLAMB", + "SOLAN": "Solana Beach", + "SOLANAP": "Solana Poker", + "SOLANAS": "Solana Swap", "SOLAPE": "SolAPE Token", "SOLAR": "Solar", "SOLARA": "Solara", "SOLARDAO": "Solar DAO", "SOLARE": "Solareum", + "SOLAREU": "Solareum", "SOLARFARM": "SolarFarm", "SOLARIX": "SOLARIX", "SOLAV": "SOLAV TOKEN", "SOLBET": "SOL STREET BETS", + "SOLBO": "SolBoss", "SOLBULL": "SOLBULL", "SOLC": "SolCard", "SOLCASH": "SOLCash", "SOLCAT": "SOLCAT", "SOLCEX": "SolCex", "SOLE": "SoleCoin", + "SOLER": "Solerium", "SOLETF": "SOL ETF", "SOLEX": "Solex Launchpad", "SOLFI": "SoliDefi", @@ -11116,13 +12748,18 @@ "SOLGUN": "Solgun", "SOLID": "Solidified", "SOLIDSEX": "SOLIDsex: Tokenized veSOLID", + "SOLITO": "SOLITO", "SOLKIT": "Solana Kit", "SOLLY": "Solly", + "SOLM": "SolMix", "SOLMATES": "SOLMATES", + "SOLME": "Solmedia", "SOLMEME": "TrumpFFIEGMEBidenCAT2024AMC", "SOLNAV": "SOLNAV AI", "SOLNIC": "Solnic", "SOLO": "Sologenic", + "SOLOR": "Solordi", + "SOLP": "SolPets", "SOLPAD": "Solpad Finance", "SOLPAKA": "Solpaka", "SOLPENG": "SOLPENG", @@ -11130,15 +12767,23 @@ "SOLS": "sols", "SOLSCC": "sols", "SOLSPONGE": "Solsponge", + "SOLT": "Soltalk AI", + "SOLTR": "SolTrump", + "SOLVBTC": "Solv Protocol SolvBTC", + "SOLVBTCBBN": "Solv Protocol SolvBTC.BBN", + "SOLVBTCCORE": "Solv Protocol SolvBTC.CORE", "SOLVE": "SOLVE", "SOLWIF": "Solwif", "SOLX": "SolarX", + "SOLXD": "Solxdex", "SOLY": "Solamander", + "SOLYMPICS": "Solympics", "SOLZILLA": "Solzilla", "SOM": "Souls of Meta", "SOMA": "Soma", "SOMM": "Sommelier", "SOMNIUM": "Somnium Space CUBEs", + "SOMPS": "SompsOnKas", "SON": "Simone", "SONAR": "SonarWatch", "SONG": "Song Coin", @@ -11147,8 +12792,10 @@ "SONICO": "Sonic", "SONICWIF": "SonicWifHat", "SONNE": "Sonne Finance", + "SONOF": "Son of Solana", "SOON": "Soonaverse", "SOONCOIN": "SoonCoin", + "SOOTCASE": "I like my sootcase", "SOP": "SoPay", "SOPHON": "Sophon (Atomicals)", "SOR": "Sorcery", @@ -11176,16 +12823,23 @@ "SP": "Sex Pistols", "SP8DE": "Sp8de", "SPA": "Sperax", + "SPAC": "SPACE DOGE", "SPACE": "Spacelens", "SPACECOIN": "SpaceCoin", + "SPACED": "SPACE DRAGON", "SPACEPI": "SpacePi", "SPAD": "SolPad", "SPAI": "Starship AI", "SPAIN": "SpainCoin", "SPANK": "SpankChain", + "SPARK": "Sparklife", + "SPARKLET": "Upland", "SPARKO": "Sparko", "SPARTA": "Spartan Protocol Token", + "SPARTACATS": "SpartaCats", + "SPARTAD": "SpartaDex", "SPAT": "Meta Spatial", + "SPAVAX": "Avalanche (Synapse Protocol)", "SPAY": "SpaceY 2025", "SPC": "SpaceChain ERC20", "SPC.QRC": "SpaceChain (QRC-20)", @@ -11198,6 +12852,7 @@ "SPEC": "SpecCoin", "SPECT": "Spectral", "SPECTRE": "SPECTRE AI", + "SPEE": "SpeedCash", "SPEEDY": "Speedy", "SPELL": "Spell Token", "SPELLFIRE": "Spellfire", @@ -11228,7 +12883,9 @@ "SPIKE": "Spiking", "SPILLWAYS": "SpillWays", "SPIN": "SPIN Protocol", + "SPINT": "Spintria", "SPIRIT": "SpiritSwap", + "SPITT": "Hawk Ttuuaahh", "SPIZ": "SPACE-iZ", "SPK": "SparksPay", "SPKL": "SpokLottery", @@ -11246,9 +12903,10 @@ "SPOODY": "Spoody Man", "SPOOF": "Spoofify", "SPOOL": "Spool DAO Token", - "SPORE": "Enoki Finance", + "SPORE": "Spore", "SPORT": "SportsCoin", "SPORTS": "ZenSports", + "SPORTSP": "SportsPie", "SPOTS": "Spots", "SPOX": "Sports Future Exchange Token", "SPRING": "Spring", @@ -11261,6 +12919,8 @@ "SPS": "Splinterlands", "SPT": "SPECTRUM", "SPUME": "Spume", + "SPUNK": "PUNK", + "SPURD": "Spurdo Spärde", "SPURDO": "spurdo", "SPURS": "Tottenham Hotspur Fan Token", "SPWN": "Bitspawn", @@ -11270,10 +12930,12 @@ "SPYRO": "SPYRO", "SQAT": "Syndiqate", "SQG": "Squid Token", + "SQGROW": "SquidGrow", "SQL": "Squall Coin", "SQR": "Magic Square", "SQT": "SubQuery Network", "SQTS": "Sqts (Ordinals)", + "SQU": "SquadSwap", "SQUA": "Square Token", "SQUAD": "Superpower Squad", "SQUATCH": "SASQUATCH", @@ -11284,7 +12946,10 @@ "SQUID2": "Squid Game 2.0", "SQUIDGROW": "SquidGrow", "SQUIDGROWV1": "SquidGrow v1", + "SQUIDV1": "Squid Game v1", + "SQUIDW": "Squidward Coin", "SQUOGE": "DogeSquatch", + "SR30": "SatsRush", "SRBP": "Super Rare Ball Potion", "SRC": "SecureCoin", "SRCH": "SolSrch", @@ -11314,10 +12979,13 @@ "SSHIP": "SSHIP", "SSLX": "StarSlax", "SSNC": "SatoshiSync", + "SSOL": "Solayer SOL", "SSS": "StarSharks", + "SSSSS": "Snake wif Hat", "SST": "SIMBA Storage Token", "SSTC": "SunShotCoin", "SSU": "Sunny Side up", + "SSUI": "Spring Staked SUI", "SSV": "ssv.network", "SSVCOIN": "SSVCoin", "SSVV1": "Blox", @@ -11325,6 +12993,7 @@ "SSX": "SOMESING", "ST": "Skippy Token", "STA": "STOA Network", + "STAB": "STABLE ASSET", "STABLZ": "Stablz", "STAC": "STAC", "STACK": "StackOS", @@ -11335,6 +13004,7 @@ "STAKEDETH": "StakeHound Staked Ether", "STALIN": "StalinCoin", "STAMP": "SafePost", + "STAN": "Stank Memes", "STANDARD": "Stakeborg DAO", "STAPT": "Ditto Staked Aptos", "STAR": "FileStar", @@ -11351,6 +13021,7 @@ "STARSHI": "Starship", "STARSHIP": "STARSHIP", "STARSHIPDOGE": "Starship Doge", + "STARSHIPONSOL": "Starship", "START": "StartCoin", "STARTA": "Starta", "STARWARS": "Star Wars", @@ -11360,6 +13031,8 @@ "STATE": "New World Order", "STATER": "Stater", "STATERA": "Statera", + "STATOK": "STA", + "STATOKEN": "STA", "STATOM": "Stride Staked ATOM", "STATS": "Stats", "STAX": "Staxcoin", @@ -11373,6 +13046,7 @@ "STEAK": "SteakHut Finance", "STEALTH": "StealthPad", "STEAMPUNK": "SteamPunk", + "STEAMX": "Steam Exchange", "STEEM": "Steem", "STEEMD": "Steem Dollars", "STEEP": "SteepCoin", @@ -11390,6 +13064,7 @@ "STEWIE": "Stewie Coin", "STEX": "STEX", "STF": "Structure Finance", + "STFLOW": "Increment Staked FLOW", "STFX": "STFX", "STG": "Stargate Finance", "STHR": "Stakerush", @@ -11399,12 +13074,14 @@ "STIMA": "STIMA", "STING": "Sting", "STINJ": "Stride Staked INJ", + "STIX": "STIX", "STJUNO": "Stride Staked JUNO", "STK": "STK Token", "STKAAVE": "Staked Aave", "STKATOM": "pSTAKE Staked ATOM", "STKBNB": "pSTAKE Staked BNB", "STKC": "Streakk Chain", + "STKD": "Stkd SCRT", "STKHUAHUA": "pSTAKE Staked HUAHUA", "STKK": "Streakk", "STKSTARS": "pSTAKE Staked STARS", @@ -11416,16 +13093,19 @@ "STND": "Standard Protocol", "STNEAR": "Staked NEAR", "STO": "Save The Ocean", + "STOC": "STO Cash", "STOG": "Stooges", "STOGE": "Stoner Doge Finance", "STOIC": "stoicDAO", "STON": "STON", "STONE": "Stone Token", + "STONEDE": "Stone DeFi", "STONK": "STONK", "STONKS": "HarryPotterObamaWallStreetBets10Inu", "STOP": "SatoPay", "STOR": "Self Storage Coin", "STORE": "Bit Store", + "STOREP": "Storepay", "STORJ": "Storj", "STORM": "Storm", "STORY": "Story", @@ -11437,7 +13117,9 @@ "STPT": "STP Network", "STQ": "Storiqa Token", "STR": "Sourceless", + "STRA": "STRAY", "STRAKS": "Straks", + "STRAT": "Strategic Hub for Innovation in Blockchain", "STRAX": "Stratis", "STRAY": "Stray Dog", "STRD": "Stride", @@ -11451,6 +13133,7 @@ "STRM": "StreamCoin", "STRNGR": "Stronger", "STRONG": "Strong", + "STRONGSOL": "Stronghold Staked SOL", "STRONGX": "StrongX", "STRP": "Strips Finance", "STRS": "STARS", @@ -11471,6 +13154,7 @@ "STUCK": "mouse in pasta", "STUD": "Studyum", "STUDENTC": "Student Coin", + "STUFF": "STUFF.io", "STUMEE": "Stride Staked UMEE", "STUSDT": "Staked USDT", "STV": "Sativa Coin", @@ -11483,6 +13167,7 @@ "STZETA": "ZetaEarn", "STZU": "Shihtzu Exchange Token", "SU": "Smol Su", + "SUB": "Subsocial", "SUBAWU": "Subawu Token", "SUBF": "Super Best Friends", "SUBS": "Substratum Network", @@ -11491,58 +13176,96 @@ "SUGAR": "Sugar Exchange", "SUI": "Sui", "SUIA": "SUIA", + "SUIB": "Suiba Inu", + "SUIJAK": "Suijak", + "SUILAMA": "Suilama", + "SUIMAN": "Suiman", + "SUIMON": "Sui Monster", "SUIP": "SuiPad", "SUISHIB": "SuiShiba", + "SUITE": "Suite", + "SUKI": "SUKI", "SUKU": "SUKU", "SULFERC": "SULFERC", "SUM": "SumSwap", + "SUMI": "SUMI", "SUMMER": "Summer", + "SUMMIT": "Summit", "SUMO": "Sumokoin", "SUN": "Sun Token", "SUNC": "Sunrise", "SUNDAE": "Sundae the Dog", + "SUNDOG": "SUNDOG", "SUNEX": "The Sun Exchange", + "SUNGOAT": "SUNGOAT", + "SUNGOU": "Sungou", "SUNI": "SUNI", + "SUNJAK": "Sunjak", + "SUNLION": "SUNLION", + "SUNMAGA": "SunMaga", + "SUNN": "Sunny on Tron", + "SUNNED": "SUNNED", + "SUNNEIRO": "SunNeiro", "SUNNY": "Sunny Aggregator", "SUNOLD": "Sun Token", + "SUNPEPE": "sunpepe", + "SUNPUMP": "To The Sun", + "SUNTRON": "TRON MASCOT", "SUNV1": "Sun Token v1", + "SUNWUKONG": "SunWukong", "SUP": "Supcoin", "SUP8EME": "SUP8EME Token", "SUPE": "Supe Infinity", "SUPER": "SuperVerse", "SUPERBID": "SuperBid", + "SUPERBONK": "SUPER BONK", "SUPERC": "SuperCoin", + "SUPERCAT": "SUPERCAT", + "SUPERF": "SUPER FLOKI", + "SUPEROETHB": "Super OETH", + "SUPERT": "Super Trump", "SUPERTX": "SuperTX", + "SUPR": "SuperDapp", "SUR": "Suretly", "SURE": "inSure", "SURF": "Surf.Finance", "SURV": "Survival Game Online", "SUSD": "sUSD", + "SUSDA": "sUSDa", "SUSDE": "Ethena Staked USDe", + "SUSDX": "Staked USDX", "SUSHI": "Sushi", + "SUSX": "Savings USX", "SUTEKU": "Suteku", "SUTER": "Suterusu", + "SUWI": "suwi", "SUZUME": "Shita-kiri Suzume", "SVD": "savedroid", + "SVETH": "Savvy ETH", "SVL": "Slash Vision Labs", "SVN": "Savanna", "SVNN": "Savanna Haus", "SVPN": "Shadow Node", "SVS": "GivingToServices SVS", "SVT": "Solvent", + "SVTS": "Syncvault", "SVX": "Savix", "SVY": "Savvy", "SWA": "Swace", "SWACH": "Swachhcoin", "SWAG": "SWAG Finance", + "SWAGGY": "swaggy", + "SWAGT": "Swag Token", "SWAI": "Safe Water AI", "SWAMP": "Swampy", + "SWAN": "Black Swan", "SWAP": "Trustswap", "SWAPP": "SWAPP Protocol", "SWAPZ": "SWAPZ.app", "SWARM": "SwarmCoin", "SWASH": "Swash", "SWAY": "Sway Social", + "SWBTC": "Swell Restaked BTC", "SWC": "Scanetchain Token", "SWCH": "SwissCheese", "SWD": "SW DAO", @@ -11550,11 +13273,14 @@ "SWEAT": "Sweat Economy", "SWEEP": "Sweeptoken", "SWEET": "SweetStake", + "SWELL": "Swell Network", "SWETH": "swETH", "SWFL": "Swapfolio", "SWFTC": "SWFTCoin", "SWG": "Swirge", + "SWGT": "SmartWorld Global", "SWH": "simbawifhat", + "SWIF": "SUNwifHat", "SWIFT": "BitSwift", "SWIFTIES": "Taylor Swift", "SWIM": "SWIM - Spread Wisdom", @@ -11564,12 +13290,14 @@ "SWIPES": "BNDR", "SWIRL": "Swirl Social", "SWIRLX": "SwirlToken", + "SWIS": "Swiss Cash Coin", "SWISE": "StakeWise", "SWITCH": "Switch", "SWM": "Swarm Fund", "SWOLE": "Swole Doge", "SWOP": "Swop", "SWORD": "eZKalibur", + "SWORLD": "Seedworld", "SWOT": "Swot AI", "SWP": "Kava Swap", "SWPR": "Swapr", @@ -11594,6 +13322,7 @@ "SYBL": "Sybulls", "SYBTC": "sBTC", "SYC": "SynchroCoin", + "SYK": "Stryke", "SYL": "XSL Labs", "SYLO": "Sylo", "SYLV": "Sylvester", @@ -11601,15 +13330,18 @@ "SYN": "Synapse", "SYNC": "Syncus", "SYNCC": "SyncCoin", + "SYNCG": "SyncGPT", "SYNCN": "Sync Network", "SYNCO": "Synco", "SYNLEV": "SynLev", "SYNO": "Synonym Finance", "SYNR": "MOBLAND", "SYNT": "Synthetix Network", + "SYNTE": "Synternet", "SYNTH": "Synthswap", "SYNX": "Syndicate", "SYPOOL": "Sypool", + "SYRUP": "Syrup", "SYS": "Syscoin", "SZCB": "Zugacoin", "T": "Threshold Network Token", @@ -11620,8 +13352,10 @@ "TABOO": "Taboo Token", "TAC": "Traceability Chain", "TACHYON": "Tachyon Protocol", - "TAD": "Tadpole Finance", + "TAD": "Tadpole", "TADA": "Ta-da", + "TADDY": "DADDY TRUMP", + "TADPOLEF": "Tadpole Finance", "TAF": "TAF", "TAGR": "Think And Get Rich Coin", "TAI": "TARS Protocol", @@ -11631,8 +13365,13 @@ "TAIYO": "Taiyo", "TAJ": "TajCoin", "TAK": "TakCoin", + "TAKE": "Take America Back", "TAKI": "Taki", + "TALA": "Baby Tala", + "TALAHON": "Talahon", "TALAO": "Talao", + "TALENT": "Talent Protocol", + "TALES": "Tales of Pepe", "TALIS": "Talis Protocol", "TALK": "Talken", "TAMA": "Tamadoge", @@ -11643,8 +13382,11 @@ "TANK": "CryptoTanks", "TANPIN": "Tanpin", "TANUKI": "Tanuki", + "TANUPAD": "Tanuki Launchpad", "TAO": "Bittensor", "TAONU": "TAO INU", + "TAOP": "TaoPad", + "TAOTOOLS": "TAOTools", "TAP": "TAP FANTASY", "TAPC": "Tap Coin", "TAPPINGCOIN": "TappingCoin", @@ -11663,6 +13405,7 @@ "TAT": "Tatcoin", "TATA": "TATA Coin", "TATE": "Tate", + "TATES": "Tate Stop", "TATSU": "Taτsu", "TAU": "Lamden Tau", "TAUC": "Taurus Coin", @@ -11670,6 +13413,7 @@ "TAUR": "Marnotaur", "TAVA": "ALTAVA", "TAX": "MetaToll", + "TAXI": "Robotaxi", "TBAC": "BlockAura", "TBANK": "TaoBank", "TBAR": "Titanium BAR", @@ -11680,6 +13424,7 @@ "TBCX": "TrashBurn", "TBD": "THE BIG DEBATE", "TBE": "TrustBase", + "TBEER": "TRON BEER", "TBFT": "Türkiye Basketbol Federasyon Token", "TBIS": "TBIS token", "TBL": "Tombola", @@ -11687,9 +13432,11 @@ "TBT": "T-BOT", "TBTC": "tBTC", "TBTCV1": "tBTC v1", + "TBULL": "Tron Bull", "TBX": "Tokenbox", "TCANDY": "TripCandy", "TCAP": "Total Crypto Market Cap", + "TCASH": "Trump Cash", "TCAT": "The Currency Analytics", "TCC": "The ChampCoin", "TCG": "Today's Crypto", @@ -11708,12 +13455,13 @@ "TCT": "TokenClub", "TCX": "T-Coin", "TCY": "The Crypto You", - "TD": "Trade Chain", + "TD": "The Big Red", "TDAN": "TDAN", "TDE": "Trade Ecology Token", "TDEFI": "Token Teknoloji A.S. Token DeFi", "TDFB": "TDFB", "TDFY": "Tidefi", + "TDM": "TDM", "TDP": "TrueDeck", "TDROP": "ThetaDrop", "TDS": "TokenDesk", @@ -11748,6 +13496,7 @@ "TENET": "TENET", "TENFI": "TEN", "TENNET": "Tennet", + "TENS": "TensorScan", "TENSHI": "Tenshi", "TENT": "TENT", "TEP": "Tepleton", @@ -11756,12 +13505,17 @@ "TERA": "TERA", "TERADYNE": "Teradyne", "TERAR": "Terareum", + "TERAV1": "Terareum v1", "TERAWATT": "Terawatt", + "TERM": "Terminal of Simpson", + "TERMINAL": "Book Terminal of Truths", + "TERMINUS": "Terminus", "TERN": "Ternio", "TERN.ETH": "Ternio ERC20", "TERR": "Terrier", "TERRA": "Terraport", "TERRAB": "TERRABYTE AI", + "TERRY": "Terry The Disgruntled Turtle", "TERZ": "SHELTERZ", "TES": "TeslaCoin", "TESLA": "TeslaCoilCoin", @@ -11774,6 +13528,7 @@ "TETRA": "Tetra", "TETU": "TETU", "TEW": "Trump in a memes world", + "TF47": "Trump Force 47", "TFBX": "Truefeedback Token", "TFC": "The Freedom Coin", "TFI": "TrustFi Network Token", @@ -11787,34 +13542,49 @@ "TGCC": "TheGCCcoin", "TGPT": "Trading GPT", "TGRAM": "TG20 TGram", + "TGRASS": "Top Grass Club", "TGT": "TargetCoin", + "TGW": "The Green World", "TH": "Team Heretics Fan Token", "THALES": "Thales", + "THAPT": "Thala APT", "THAVAGE": "Mike Tython", "THC": "The Hempcoin", + "THD": "Trump Harris Debate", "THE": "The Protocol", "THE9": "THE9", + "THEAICOIN": "AI", + "THEB": "The Boys Club", "THEBLOX": "The Blox Project", + "THEC": "The CocktailBar", "THECA": "Theca", + "THECAT": "THECAT", "THECITADEL": "The Citadel", "THEDAO": "The DAO", + "THEF": "The Flash Currency", + "THEG": "The GameHub", "THEHARAMBE": "Harambe", + "THEM": "The Meta DAO", "THEMIS": "Themis", "THEN": "THENA", "THEO": "Theopetra", "THEOS": "Theos", + "THES": "The Standard Protocol (USDS)", "THETA": "Theta Network", + "THETAN": "Thetan Coin", "THETRIBE": "The Tribe", "THEX": "Thore Exchange", "THG": "Thetan Arena", "THIK": "ThikDik", "THING": "Nothing", "THINKWAREAI": "ThinkwareAI", + "THISISF": "This is Fine", "THL": "Thala", "THN": "Throne", "THNX": "ThankYou", "THO": "Athero", "THOL": "AngelBlock", + "THOLA": "Tholana", "THOR": "THORSwap", "THOREUM": "Thoreum V3", "THP": "TurboHigh Performance", @@ -11825,6 +13595,7 @@ "THS": "TechShares", "THT": "Thought", "THUG": "Thug Life", + "THUN": "Thunder Brawl", "THUNDER": "ThunderStake", "THX": "Thorenext", "TI": "Titanium22", @@ -11832,11 +13603,14 @@ "TIANHE": "Tianhe", "TIC": "TrueInvestmentCoin", "TIDAL": "Tidal Finance", + "TIDDIES": "TIDDIES", "TIDE": "Tidalflats", "TIE": "Ties Network", + "TIEDAN": "TieDan", + "TIF": "This Is Fine", "TIFI": "TiFi Token", "TIG": "Tigereum", - "TIGER": "JungleKing TigerCoin", + "TIGER": "TIGER", "TIGERC": "TigerCash", "TIGERMOON": "TigerMoon", "TIGRA": "Tigra", @@ -11847,6 +13621,7 @@ "TIKTOKEN": "TikToken", "TIM": "TIMTIM GAMES", "TIME": "Chrono.tech", + "TIMES": "DARKTIMES", "TIMI": "Timicoin", "TIN": "Token IN", "TINC": "Tiny Coin", @@ -11855,6 +13630,7 @@ "TINY": "TinyBits", "TIOX": "TIOx", "TIP": "Tip Blockchain", + "TIPC": "Tipcoin", "TIPINU": "Tip Inu", "TIPS": "FedoraCoin", "TIPSY": "TipsyCoin", @@ -11865,6 +13641,7 @@ "TITANX": "TitanX", "TITC": "TitCoin", "TITI": "TiTi Protocol", + "TITS": "We Love Tits", "TITTY": "TamaKitty", "TIUSD": "TiUSD", "TIX": "Blocktix", @@ -11876,7 +13653,7 @@ "TKING": "Tiger King", "TKINU": "Tsuki Inu", "TKMN": "Tokemon", - "TKN": "Monolith", + "TKN": "Token Name Service", "TKO": "Tokocrypto", "TKP": "TOKPIE", "TKR": "CryptoInsight", @@ -11891,6 +13668,7 @@ "TLOS": "Telos", "TLP": "TulipCoin", "TLW": "TILWIKI", + "TMAGA": "THE MAGA MOVEMENT", "TMANIA": "Trump Mania", "TME": "Timereum", "TMED": "MDsquare", @@ -11899,6 +13677,7 @@ "TMNG": "TMN Global", "TMNT": "TMNT", "TMON": "Two Monkey Juice Bar", + "TMPL": "TMPL", "TMRW": "TMRW Coin", "TMSH": "Bursaspor Fan Token", "TMT": "Tamy Token", @@ -11922,6 +13701,7 @@ "TODD": "TURBO TODD", "TOK": "Tokenplace", "TOKA": "Tonka Finance", + "TOKAMAK": "Tokamak Network", "TOKAU": "Tokyo AU", "TOKC": "Tokyo Coin", "TOKE": "Tokemak", @@ -11931,6 +13711,7 @@ "TOKKI": "CRYPTOKKI", "TOKO": "ToKoin", "TOKU": "TokugawaCoin", + "TOKUD": "Tokuda", "TOL": "Tolar", "TOLO": "Tolo Yacoloco", "TOLYCAT": "Toly's Cat", @@ -11938,48 +13719,64 @@ "TOMAHAWKCOIN": "Tomahawkcoin", "TOMAN": "IRR", "TOMB": "Tomb", + "TOMC": "TOM CAT", "TOMI": "tomiNet", "TOMOE": "TomoChain ERC20", "TOMS": "TomTomCoin", - "TON": "Tokamak Network", + "TON": "Toncoin", "TONALD": "Tonald Trump", - "TONCOIN": "The Open Network", "TONE": "TE-FOOD", "TONI": "Daytona Finance", "TONIC": "Tectonic", "TONK": "Tonk Inu", "TONNEL": "TONNEL Network", "TONS": "TONSniper", + "TONST": "Ton Stars", + "TONT": "TONKIT", "TONTOKEN": "TONToken", "TONUP": "TonUP", "TONY": "TONY THE DUCK", "TOOB": "Toobcoin", + "TOOBIGTORIG": "Too Big To Rig", "TOOKER": "tooker kurlson", "TOOLS": "TOOLS", "TOON": "Pontoon", "TOONF": "Toon Finance", "TOPC": "Topchain", + "TOPCA": "TOP CAT", + "TOPCAT": "Topcat", "TOPG": "Tate Token", + "TOPGP": "TOP G PEPE", + "TOPI": "Topi Meme", "TOPIA": "Hytopia", "TOPN": "TOP Network", "TOR": "TOR", + "TORA": "TORA NEKO", + "TORCH": "Hercules Token", "TORE": "Toreus Finance", "TORG": "TORG", "TORI": "Teritori", "TORII": "Torii Finance", "TORN": "Tornado Cash", + "TORO": "Toro Inoue", + "TORSY": "TORSY", "TOS": "ThingsOperatingSystem", "TOSA": "TosaInu BSC", "TOSC": "T.OS", "TOSDIS": "TosDis", "TOSHE": "Toshe", "TOSHI": "Toshi", + "TOSHKIN": "Toshkin Coin", "TOT": "TotCoin", "TOTEM": "DragonMaster", "TOTM": "Totem", "TOTO": "TOTO", "TOUCHFAN": "TouchFan", + "TOUCHG": "Touch Grass", + "TOUR": "Tour Billion", + "TOURI": "Tourist Token", "TOURISTS": "TOURIST SHIBA INU", + "TOWELI": "Towelie", "TOWER": "Tower", "TOWN": "Town Star", "TOX": "INTOverse", @@ -12006,12 +13803,16 @@ "TRACN": "trac (Ordinals)", "TRADE": "Polytrade", "TRADEBOT": "TradeBot", + "TRADECHAIN": "Trade Chain", "TRADEX": "TradeX AI", "TRAID": "Traid", + "TRAIMP": "TRUMP AI", "TRAIN": "Trump Train", "TRAK": "TrakInvest", + "TRALA": "TRALA", "TRANQ": "Tranquil Finance", "TRANS": "Trans Pepe", + "TRANSFER": "TransferCoin", "TRAT": "Tratok", "TRAVA": "Trava Finance", "TRAXIA": "Traxia Membership Token", @@ -12036,6 +13837,7 @@ "TRET": "Tourist Review", "TRG": "The Rug Game", "TRGI": "The Real Golden Inu", + "TRHUB": "Tradehub", "TRI": "Triangles Coin", "TRIA": "Triaconta", "TRIAS": "Trias", @@ -12048,18 +13850,28 @@ "TRINI": "Trinity Network Credit", "TRIO": "Tripio", "TRIPAD": "TripAdvisor, Inc.", + "TRITON": "Triton", "TRIVIA": "Trivians", "TRIX": "TriumphX", "TRK": "TruckCoin", + "TRKX": "Trakx", "TRL": "Triall", "TRMX": "TourismX Token", "TRNDZ": "Trendsy", + "TRNGUY": "Tron Guy Project", "TROG": "Trog", + "TROGE": "Troge", "TROLL": "Trollcoin", "TROLLHEIM": "Trollheim", + "TROLLICTO": "TROLLI CTO", "TROLLMODE": "TROLL MODE", + "TROLLS": "trolls in a memes world", + "TRONDOG": "TronDog", + "TRONI": "Tron Inu", + "TRONP": "Donald Tronp", "TRONPAD": "TRONPAD", "TROP": "Interop", + "TROPPY": "TROPPY", "TROSS": "Trossard", "TROVE": "Arbitrove Governance Token", "TROY": "Troy", @@ -12070,35 +13882,59 @@ "TRTL": "TurtleCoin", "TRTT": "Trittium", "TRU": "TrueFi", + "TRUAPT": "TruFin Staked APT", "TRUCE": "WORLD PEACE PROJECT", "TRUE": "True Chain", "TRUEBIT": "Truebit Protocol", "TRUF": "Truflation", + "TRUFV1ERC20": "Truflation v1 ERC-20", "TRUM": "TrumpBucks", "TRUMAGA": "TrumpMAGA", "TRUMATIC": "TruFin Staked MATIC", "TRUMP": "MAGA", + "TRUMP2": "Trump2024", "TRUMP2024": "Donald Trump", + "TRUMP3": "Trump MP3", + "TRUMP47": "47th President of the United States", + "TRUMPA": "TRUMP AI", + "TRUMPAMANIA": "TRUMPAMANIA", "TRUMPARMY": "Trump Army", + "TRUMPBASE": "MAGA (magatrumponbase.tech)", "TRUMPBIDEN": "Trump vs Biden", + "TRUMPC": "TrumpCat", + "TRUMPCA": "Trump Card", "TRUMPCAT": "TRUMPCAT", + "TRUMPCATS": "Trump Golden Cat", "TRUMPCOIN": "TrumpCoin", + "TRUMPDAO": "TRUMP DAO", "TRUMPDO": "TRUMP", "TRUMPDOGE": "Trump Doge", "TRUMPE": "Trump Pepe", "TRUMPEPE": "Trump Pepe", + "TRUMPER": "Trump Era", + "TRUMPF": "Trump Fight", "TRUMPHAT": "Trump Hat", "TRUMPINU": "Trump Inu", + "TRUMPJ": "TRUMPJR", "TRUMPJR": "TrumpJr", + "TRUMPM": "TRUMP MAGA PRESIDENT", + "TRUMPMA": "TRUMP MAGA SUPER", + "TRUMPMAGA": "President Trump MAGA", + "TRUMPONBASE": "TRUMP ON BASE", + "TRUMPS": "Trump SOL", + "TRUMPSB": "TrumpsBags", + "TRUMPSFIGHT": "TrumpsFight", "TRUMPSHIBA": "Trump Shiba", "TRUMPTECH": "Trump Tech", "TRUMPTITANS": "TrumpTitans", + "TRUMPVANCE": "Trump Vance 2024", "TRUMPX": "Trump X-Maga", "TRUMPZ": "Trump Zhong", "TRUNK": "Elephant Money", "TRUST": "TrustDAO", "TRUSTNFT": "TrustNFT", "TRUTH": "TruthGPT", + "TRUTHFI": "Truthfi", "TRV": "TrustVerse", "TRVC": "Trivechain", "TRVL": "TRVL", @@ -12107,6 +13943,7 @@ "TRXC": "TRONCLASSIC", "TRXDICE": "TRONdice", "TRXS": "Staked TRX", + "TRXV1": "TRON V1", "TRXWIN": "TronWin", "TRYB": "BiLira", "TRYC": "TRYC", @@ -12122,10 +13959,12 @@ "TSHARE": "Tomb Shares", "TSHP": "12Ships", "TSL": "Energo", + "TSLT": "Tamkin", "TSN": "Tsunami Exchange Token", "TSR": "Tesra", "TSUBASAUT": "TSUBASA Utility Token", "TSUGT": "Captain Tsubasa", + "TSUJI": "Tsutsuji", "TSUKA": "Dejitaru Tsuka", "TSX": "TradeStars", "TT": "ThunderCore", @@ -12135,8 +13974,10 @@ "TTM": "To The Moon", "TTN": "Titan Coin", "TTT": "The Transfer Token", + "TTTU": "T-Project", "TTU": "TaTaTu", "TTV": "TV-TWO", + "TUA": "Atua AI", "TUBE": "BitTube", "TUBES": "TUBES", "TUCKER": "TUCKER CARLSON", @@ -12148,20 +13989,24 @@ "TUNE": "Bitune", "TUP": "Tenup", "TUR": "Turron", - "TURBO": "Turbo Wallet", + "TURB": "TurboX", + "TURBO": "Turbo", + "TURBOB": "Turbo Browser", "TURBOS": "Turbos Finance", - "TURBOT": "Turbo", + "TURBOW": "Turbo Wallet", "TURT": "TurtSat", "TUS": "Treasure Under Sea", "TUSD": "True USD", "TUSDV1": "True USD v1", "TUT": "Tutellus", "TUTTER": "Tutter", + "TUX": "Tux The Penguin", "TUZKI": "Tuzki", "TUZLA": "Tuzlaspor Token", "TVK": "Terra Virtua Kolect", "TVNT": "TravelNote", "TVRS": "TiraVerse", + "TVS": "TVS", "TW": "Winners Coin", "TWC": "Twilight", "TWD": "Terra World Token", @@ -12169,7 +14014,9 @@ "TWEETY": "Tweety", "TWELVE": "TWELVE ZODIAC", "TWEP": "The Web3 Project", + "TWIF": "Tomwifhat", "TWIFB": "TrumpWifBiden", + "TWIGGY": "Twiggy", "TWIN": "Twinci", "TWIST": "TwisterCoin", "TWLV": "Twelve Coin", @@ -12177,12 +14024,14 @@ "TWOGE": "Twoge Inu", "TWP": "TrumpWifPanda", "TWT": "Trust Wallet Token", + "TWURTLE": "twurtle the turtle", "TX": "Tradix", "TX20": "Trex20", "TXA": "TXA", "TXAG": "tSILVER", "TXAU": "tGOLD", "TXBIT": "Txbit Token", + "TXC": "TEXITcoin", "TXG": "TRUSTxGAMING", "TXL": "Autobahn Network", "TXT": "TuneTrade", @@ -12191,14 +14040,19 @@ "TYBGSc": "Base Goddess", "TYC": "Tycoon", "TYCOON": "CryptoTycoon", + "TYKE": "Tyke The Elephant", + "TYLER": "Tyler", "TYOGHOUL": "TYO GHOUL", "TYPE": "TypeAI", "TYPERIUM": "Typerium", + "TYPUS": "Typus", "TYRANT": "Fable Of The Dragon", "TYRION": "Tyrion", + "TYSON": "Mike Tyson", "TYT": "Tianya Token", "TZC": "TrezarCoin", "TZKI": "Tsuzuki Inu", + "TZU": "Sun Tzu", "U": "Unidef", "U8D": "Universal Dollar", "UAEC": "United Arab Emirates Coin", @@ -12240,6 +14094,7 @@ "UDOO": "Hyprr", "UDS": "Undeads Games", "UDT": "Unlock Protocol", + "UE": "UE Coin", "UEC": "United Emirates Coin", "UEDC": "United Emirate Decentralized Coin", "UENC": "UniversalEnergyChain", @@ -12252,6 +14107,7 @@ "UFO": "UFO Gaming", "UFOC": "Unknown Fair Object", "UFOCOIN": "Uniform Fiscal Object", + "UFOP": "UFOPepe", "UFR": "Upfiring", "UFT": "UniLend Finance", "UGAS": "Ultrain", @@ -12273,6 +14129,7 @@ "ULTI": "Ultiverse", "ULTIMA": "Ultima", "ULTIMATEBOT": "Ultimate Tipbot", + "ULTR": "ULTRA MAGA", "ULTRA": "Ultra", "ULTRAP": "ULTRA Prisma Finance", "ULX": "ULTRON", @@ -12284,7 +14141,10 @@ "UMBR": "Umbria Network", "UMC": "Umbrella Coin", "UMI": "Universal Money Instrument", + "UMID": "Umi Digital", + "UMJA": "Umoja", "UMK": "UMKA", + "UMM": "UMM", "UMMA": "UMMA Token", "UMO": "Universal Molecule", "UMT": "UnityMeta", @@ -12296,14 +14156,17 @@ "UNBREAKABLE": "UnbreakableCoin", "UNC": "UnCoin", "UNCL": "UNCL", + "UNCN": "Unseen", "UNCX": "UniCrypt", "UND": "United Network Distribution", "UNDB": "unibot.cash", + "UNDE": "Undead Finance", "UNDEAD": "Undead Blocks", "UNDG": "UniDexGas", "UNDX": "UNODEX", "UNF": "Unfed Coin", "UNFI": "Unifi Protocol DAO", + "UNFK": "UNFK", "UNI": "Uniswap Protocol Token", "UNIBOT": "Unibot", "UNIC": "Unicly", @@ -12311,13 +14174,16 @@ "UNICORN": "UNICORN Token", "UNIDEXAI": "UniDexAI", "UNIDX": "UniDex", + "UNIE": "Uniswap Protocol Token (Avalanche Bridge)", "UNIETH": "Universal ETH", "UNIFY": "Unify", "UNIM": "Unicorn Milk", + "UNIO": "Unio Coin", "UNIQ": "Uniqredit", "UNIQUE": "Unique One", "UNISTAKE": "Unistake", "UNIT": "Universal Currency", + "UNIT0": "UNIT0", "UNITARYSTATUS": "UnitaryStatus Dollar", "UNITED": "UnitedCoins", "UNITRADE": "UniTrade", @@ -12370,11 +14236,15 @@ "URS": "URUS", "URUS": "Urus Token", "URX": "URANIUMX", - "USA": "DEDPRZ", + "USA": "Based USA", + "USACOIN": "American Coin", "USAT": "USAT", + "USBT": "Universal Blockchain", "USC": "Ultimate Secure Cash", "USCC": "USC", "USCOIN": "USCoin", + "USD0": "Usual", + "USD3": "Web 3 Dollar", "USDA": "USDA", "USDAP": "Bond Appetite USD", "USDB": "USD Bancor", @@ -12382,6 +14252,14 @@ "USDBLAST": "USDB Blast", "USDC": "USD Coin", "USDCASH": "USDCASH", + "USDCAT": "UpSideDownCat", + "USDCAV": "USD Coin (Portal from Avalanche)", + "USDCBS": "USD Coin (Portal from BSC)", + "USDCE": "USD Coin (Avalanche Bride)", + "USDCEAV": "USD.e Coin (Portal from Avalanche)", + "USDCET": "USD Coin (Portal from Ethereum)", + "USDCPO": "USD Coin (PoS) (Portal from Polygon)", + "USDCSO": "USD Coin (Portal from Solana)", "USDD": "USDD", "USDE": "Ethena USDe", "USDEBT": "USDEBT", @@ -12392,16 +14270,20 @@ "USDI": "Interest Protocol USDi", "USDJ": "USDJ", "USDK": "USDK", + "USDL": "Lift Dollar", "USDM": "Mountain Protocol", + "USDMA": "USD mars", "USDN": "Neutrino USD", "USDO": "USD Open Dollar", "USDP": "Pax Dollar", "USDPLUS": "Overnight.fi USD+", "USDQ": "USDQ", "USDR": "Real USD", - "USDS": "StableUSD", + "USDS": "Sky Dollar", "USDSB": "USDSB", + "USDSTABLY": "StableUSD", "USDT": "Tether", + "USDTBASE": "USDT (Base)", "USDTV": "TetherTV", "USDTZ": "USDtez", "USDU": "Upper Dollar", @@ -12411,6 +14293,7 @@ "USDZ": "Zedxion USDZ", "USE": "Usechain Token", "USEDCAR": "A Gently Used 2001 Honda", + "USETH": "USETH", "USG": "USGold", "USH": "unshETHing_Token", "USHARK": "uShark", @@ -12421,22 +14304,30 @@ "USNBT": "NuBits", "USNOTA": "NOTA", "USP": "USP Token", + "USPEPE": "American pepe", "USPLUS": "Fluent Finance", + "USR": "Resolv USR", + "USSD": "Autonomous Secure Dollar", "UST": "Wrapped UST Token", "USTB": "Superstate Short Duration U.S. Government Securities Fund", + "USTBL": "Spiko US T-Bills Money Market Fund", "USTC": "TerraClassicUSD", "USTCW": "TerraClassicUSD Wormhole", "USTX": "UpStableToken", + "USUAL": "Usual", "USV": "Universal Store of Value", "USX": "USX Quantum", + "USYC": "Hashnote USYC", "UT": "Ulord", "UTBAI": "UTB.ai", "UTC": "UltraCoin", "UTG": "UltronGlow", "UTH": "Uther", + "UTHX": "Utherverse", "UTI": "Unicorn Technology International", "UTIL": "Utility Coin", "UTK": "Utrust", + "UTKV1": "Utrust", "UTMDOGE": "UltramanDoge", "UTNP": "Universa", "UTT": "United Traders Token", @@ -12450,6 +14341,7 @@ "UWU": "UwU Lend", "UWUCOIN": "uwu", "UX": "Umee", + "UXLINK": "UXLINK", "UXOS": "UXOS", "UXP": "UXD Protocol", "UZUMAKI": "Uzumaki Inu", @@ -12467,11 +14359,14 @@ "VALID": "Validator Token", "VALOR": "Valor Token", "VALORBIT": "Valorbit", + "VALU": "Value", "VALUE": "Value Liquidity", "VAMPIRE": "Vampire Inu", "VAN": "Vanspor Token", "VANA": "Nirvana", "VANCAT": "Vancat", + "VANCE": "JD Vance", + "VANF": "Van Fwogh", "VANRY": "Vanar Chain", "VANT": "Vanta Network", "VANY": "Vanywhere", @@ -12480,6 +14375,7 @@ "VARA": "Vara Network", "VARIUS": "Varius", "VARK": "Aardvark", + "VATO": "vanitis", "VATR": "Vatra INU", "VATRENI": "Croatian FF Fan Token", "VAULT": "Vault Tech", @@ -12538,11 +14434,13 @@ "VEMP": "vEmpire DDAO", "VEN": "VeChain Old", "VENA": "Vena Network", + "VENKO": "VENKO", "VENOM": "Venom", "VENOMAI": "VENOM", "VENT": "Vent Finance", "VENTI": "VentiSwap", "VENTION": "Vention", + "VENTU": "Venture Coin", "VENUS": "VenusEnergy", "VEO": "Amoveo", "VER": "VersalNFT", @@ -12553,6 +14451,7 @@ "VERSA": "Versa Token", "VERSACE": "VERSACE", "VERSE": "Verse", + "VERTAI": "Vertical AI", "VERTEX": "Vertex", "VERUM": "Verum Coin", "VERVE": "Verve", @@ -12569,6 +14468,7 @@ "VFIL": "Venus Filecoin", "VFOX": "VFOX", "VFT": "Value Finance", + "VG": "Viu Ganhou", "VGO": "Vagabond", "VGX": "Voyager Token", "VHC": "Vault Hill City", @@ -12576,6 +14476,7 @@ "VIA": "ViaCoin", "VIB": "Viberate", "VIBE": "VIBEHub", + "VIBEA": "Vibe AI", "VIBLO": "VIBLO", "VIC": "Viction", "VICA": "ViCA Token", @@ -12585,6 +14486,7 @@ "VICTORIUM": "Victorium", "VID": "VideoCoin", "VIDA": "Vidiachange", + "VIDEO": "Videocoin by Drakula", "VIDT": "VIDT Datalink", "VIDY": "Vidy", "VIDYA": "Vidya", @@ -12593,6 +14495,7 @@ "VIEW": "Viewly", "VIG": "TheVig", "VIK": "VIKTAMA", + "VIKITA": "VIKITA", "VIKKY": "VikkyToken", "VIM": "VicMove", "VIN": "VinChain", @@ -12611,18 +14514,24 @@ "VISIO": "Visio", "VISION": "VisionGame", "VISR": "Visor", + "VIST": "VISTA", + "VISTA": "Ethervista", + "VISTADOG": "VISTADOG", "VIT": "Vision Industry Token", "VITA": "VitaDAO", "VITAE": "Vitae", "VITAFAST": "Molecules of Korolchuk IP-NFT", "VITAL": "Vital Network", + "VITALI": "Vitalik's Casper", "VITE": "VITE", "VITRA": "Vitra Studios", "VITY": "Vitteey", "VIU": "Viuly", + "VIVEK": "Head of D.O.G.E", "VIVID": "Vivid Coin", "VIVO": "VIVO Coin", "VIX": "VIXCO", + "VIX7": "VIX777", "VIXV1": "VIXCO v1", "VIZ": "Vision City", "VIZION": "ViZion Protocol", @@ -12652,6 +14561,7 @@ "VNM": "Venom", "VNN": "VINU Network", "VNO": "Veno Finance", + "VNST": "VNST Stablecoin", "VNT": "VNT Chain", "VNTW": "Value Network Token", "VNX": "VisionX", @@ -12660,9 +14570,12 @@ "VNY": "Vanity", "VOCARE": "Vocare ex Machina", "VOCO": "Provoco", + "VODCAT": "VODKA CAT", "VODKA": "Vodka Token", + "VOIP": "Voip Finance", "VOISE": "Voise", "VOL": "Volume Network", + "VOLBOOST": "VolBoost", "VOLLAR": "Vollar", "VOLR": "Volare Network", "VOLT": "Volt Inu", @@ -12731,6 +14644,7 @@ "VTN": "Voltroon", "VTOS": "VTOS", "VTRA": " E.C. Vitoria Fan Token", + "VTRAD": "VTRADING", "VTRO": "Vitruveo DEX", "VTRUMP": "Vote Trump", "VTRX": "Venus TRX", @@ -12742,18 +14656,22 @@ "VUC": "Virta Unique Coin", "VULC": "Vulcano", "VUNI": "Venus UNI", + "VUSD": "Virtual USD", "VUZZ": "Vuzz AI", "VV": "Virtual Versions", + "VVAIFU": "Dasha", "VVI": "VV Coin", "VVS": "VVS Finance", "VX": "ViteX Coin", "VXL": "Voxel X Network", + "VXR": "Vox Royale", "VXRP": "Venus XRP", "VXT": "Voxto Amplify", "VXV": "Vectorspace AI", "VYBE": "Vybe", "VYFI": "VyFinance", "VYNC": "VYNK Chain", + "VYPER": "VYPER.WIN", "VZT": "Vezt", "W": "Wormhole", "W1": "W1", @@ -12764,12 +14682,15 @@ "W3S": "Web3Shot", "W3W": "Web3 Whales", "W8BIT": "8Bit Chain", + "WAAC": "Wrapped AyeAyeCoin", "WAB": "WABnetwork", "WABI": "WABI", + "WABU": "Warrenbuffett", "WACME": "Wrapped Accumulate", "WACO": "Waste Digital Coin", "WAD": "WardenSwap", "WADA": "Wrapped Cardano", + "WAFC": "Wrapped Arsenal FC (Kayen)", "WAFFLES": "Waffles Davincij15's Cat", "WAG": "WagyuSwap", "WAGE": "Digiwage", @@ -12785,8 +14706,11 @@ "WAIFU": "Waifu", "WAIT": "Hourglass", "WAL": "The Wasted Lands", + "WALE": "Waletoken", "WALK": "Walk Token", + "WALL": "Du Rove's Wall", "WALLET": "Ambire Wallet", + "WALLI": "WALLi", "WALLY": "Wally Bot", "WALTER": "walter", "WALV": "Alvey Chain", @@ -12796,9 +14720,11 @@ "WANA": "Wanaka Farm", "WANATHA": "Wrapped ANATHA", "WAND": "WandX", + "WANK": "Wojak The Wanker", "WANKO": "WANKO•MANKO•RUNES", "WANNA": "Wanna Bot", "WANUSDT": "wanUSDT", + "WAP": "Wet Ass Pussy", "WAR": "WeStarter", "WARP": "WarpCoin", "WARPED": "Warped Games", @@ -12811,16 +14737,22 @@ "WASSIE": "WASSIE", "WASTR": "Wrapped Astar", "WAT": "Wat", + "WATC": "WATCoin", + "WATCH": "Yieldwatch", "WATER": "doginwotah", "WAVAX": "Wrapped AVAX", "WAVES": "Waves", + "WAVL": "Wrapped Aston Villa", + "WAWA": "Wawa Cat", "WAXE": "WAXE", "WAXL": "Wrapped Axelar", "WAXP": "Worldwide Asset eXchange", + "WAXS": "Axie Infinity Shards (Wormhole)", "WAY": "WayCoin", "WAZ": "MikeAI", "WBB": "Wild Beast Coin", "WBBC": "Wibcoin", + "WBC": "WorldBrain Coin", "WBCH": "Wrapped Bitcoin Cash", "WBESC": "Wrapped BESC", "WBET": "Wavesbet", @@ -12832,6 +14764,7 @@ "WBOND": "War Bond Token", "WBONE": "Shibarium Wrapped BONE", "WBONES": "Wrapped BONES", + "WBONK": "BONK (Portal Bridge)", "WBS": "Websea", "WBT": "WhiteBIT Token", "WBTC": "Wrapped Bitcoin", @@ -12851,11 +14784,13 @@ "WCKB": "Wrapped Nervos Network", "WCOIN": "WCoin", "WCORE": "Wrapped Core", + "WCRO": "Wrapped CRO", "WCS": "Weecoins", "WCSOV": "Wrapped CrownSterling", "WCT": "Waves Community Token", "WCT1WCT1": "Wrapped Car Token 1", "WCUSD": "Wrapped Celo Dollar", + "WDAI": "Dai (Wormhole)", "WDC": "WorldCoin", "WDOG": "Winterdog", "WDOGE": "Wrapped Dogecoin", @@ -12877,31 +14812,41 @@ "WEC": "Whole Earth Coin", "WECO": "WECOIN", "WED": "Wednesday Inu", + "WEEBS": "Weebs", "WEETH": "Wrapped eETH", + "WEEX": "WEEX Token", "WEF": "DOG WIF CHINESE HAT", "WEFI": "WeFi", "WEGEN": "WeGen Platform", "WEGI": "Wegie", "WEGLD": "Wrapped EGLD", + "WEHMND": "Wrapped eHMND", "WEIRDO": "Weirdo", + "WEL": "Welsh Corgi", "WELA": "Wrapped Elastos", "WELD": "Weld", "WELL": "Moonwell", "WELLTOKEN": "Well", + "WELLV1": "Moonwell v1", "WELSH": "Welshcorgicoin", "WELT": "Fabwelt", "WELUPS": "Welups Blockchain", "WEMIX": "WEMIX", "WEMIXUSD": "WEMIX", "WEN": "Wen", + "WEND": "Wellnode", + "WENIS": "WenisCoin", "WENLAMBO": "Wenlambo", "WEOS": "Wrapped EOS", + "WEPC": "World Earn & Play Community", "WEST": "Waves Enterprise", "WET": "WeShow Token", "WETH": "WETH", "WETHV1": "WETH v1", "WETHW": "Wrapped EthereumPoW", "WEVE": "veDAO", + "WEVER": "Wrapped Ever", + "WEVERV1": "Wrapped Ever v1", "WEVMOS": "Wrapped Evmos", "WEWE": "WEWE", "WEX": "WaultSwap", @@ -12909,11 +14854,13 @@ "WEXPOLY": "WaultSwap Polygon", "WFAI": "WaifuAI", "WFBTC": "Wrapped Fantom Bitcoin", + "WFDP": "WFDP", "WFIL": "Wrapped Filecoin", "WFLAMA": "WIFLAMA", "WFLOW": "Wrapped Flow", "WFO": "WoofOracle", "WFT": "Windfall Token", + "WFTM": "Wrapped Fantom", "WFTN": "Wrapped FTN", "WFUSE": "Wrapped Fuse", "WFX": "WebFlix", @@ -12926,14 +14873,20 @@ "WGR": "Wagerr", "WGRT": "WaykiChain Governance Coin", "WGT": "Web3Games.com", + "WHA": "WHALES DOGE", + "WHAL": "WHALEBERT", "WHALE": "WHALE", "WHALES": "Whales Market", + "WHAT": "What the Duck", "WHBAR": "Wrapped HBAR", + "WHC": "Whales Club", "WHEAT": "Wheat Token", "WHEE": "WHEE (Ordinals)", "WHEEL": "Wheelers", "WHEN": "WhenHub", "WHEX": "Whale Exploder", + "WHI": "White Boy Summer", + "WHINE": "Whine Coin", "WHIRL": "Whirl Finance", "WHISK": "Whiskers", "WHISKEY": "WHISKEY", @@ -12945,18 +14898,26 @@ "WHTETGRMOON": "WHITE TIGER MOON", "WHTGRPXL": "White Tiger Pixel", "WHX": "WHITEX", + "WHY": "WHY", + "WHYCAT": "WhyCat", "WIB": "Wibson", + "WIBE": "Wibegram", "WIC": "Wi Coin", "WICC": "WaykiChain", + "WICKED": "Wicked", "WIF": "dogwifhat", "WIF2": "DogWif2.0", "WIFB": "dogwifball", + "WIFC": "dogwifceo", + "WIFCAT": "WIFCAT COIN", "WIFE": "Wifejak", + "WIFEAR": "TRUMP WIF EAR", "WIFEDOGE": "Wifedoge", "WIFI": "WiFi Map", "WIFICOIN": "Wifi Coin", "WIFS": "dogwifscarf", "WIFSA": "dogwifsaudihat", + "WIGL": "Wigl", "WIGO": "WigoSwap", "WIK": "Wicked Bet", "WIKEN": "Project WITH", @@ -12964,14 +14925,18 @@ "WILC": "Wrapped ILCOIN", "WILD": "Wilder World", "WILDC": "Wild Crypto", + "WILDCOIN": "WILDCOIN", "WIN": "WINk", + "WINB": "WINBIT CASINO", "WINE": "WineCoin", "WING": "Wing Finance", "WINGS": "Wings DAO", "WINK": "Wink", "WINN": "Winnerz", + "WINNIE": "Winnie the Poodle", "WINR": "JustBet", "WINRY": "Winry Inu", + "WINSTON": "Winston", "WINT": "WinToken", "WINTER": "Winter", "WINU": "Walter Inu", @@ -12986,6 +14951,7 @@ "WIT": "Witnet", "WITCH": "Witch", "WITCOIN": "Witcoin", + "WIWI": "Wiggly Willy", "WIX": "Wixlar", "WIZA": "Wizardia", "WJD": "WJD", @@ -12998,6 +14964,7 @@ "WKD": "Wakanda Inu", "WLD": "Worldcoin", "WLF": "Wolfs Group", + "WLFI": "World Liberty Financial", "WLITI": "wLITI", "WLK": "Wolk", "WLKN": "Walken", @@ -13010,9 +14977,11 @@ "WMB": "WatermelonBlock", "WMC": "WMCoin", "WMEMO": "Wonderful Memories", + "WMETIS": "Wrapped Metis", "WMF": "Whale Maker Fund", "WMINIMA": "Wrapped Minima", "WMLX": "Millix", + "WMM": "Weird Medieval Memes", "WMN": "WebMind Network", "WMNT": "Wrapped Mantle", "WMOXY": "Moxy", @@ -13042,14 +15011,16 @@ "WOJ": "Wojak Finance", "WOJAK": "Wojak", "WOJAK2": "Wojak 2.0 Coin", + "WOJAKC": "Wojak Coin", "WOKB": "Wrapped OKB", "WOKT": "Wrapped OKT", "WOL": "World of Legends", - "WOLF": "Landwolf", + "WOLF": "LANDWOLF (AVAX)", "WOLFILAND": "Wolfiland", "WOLFOF": "Wolf of Wall Street", "WOLFP": "Wolfpack Coin", "WOLFY": "WOLFY", + "WOLT": "Wolt", "WOLVERINU": "WOLVERINU", "WOM": "WOM", "WOMB": "Wombat Exchange", @@ -13069,16 +15040,21 @@ "WOOO": "wooonen", "WOOOOO": "Wooooo! Coin", "WOOP": "Woonkly Power", + "WOOPV1": "Woonkly Power", "WOP": "WorldPay", "WOR": "Hollywood Capital Group WARRIOR", "WORK": "Work X", + "WORKE": "Worken", + "WORL": "World Record Banana", "WORLD": "World Token", "WORM": "HealthyWorm", "WORX": "Worx", "WOS": "Wolf Of Solana", + "WOT": "World Of Trump", "WOW": "WOWswap", "WOWS": "Wolves of Wall Street", "WOZX": "Efforce", + "WPAY": "WPAY", "WPC": "WePiggy Coin", "WPE": "OPES (Wrapped PE)", "WPEPE": "Wrapped Pepe", @@ -13086,6 +15062,7 @@ "WPKT": "Wrapped PKT", "WPLS": "Wrapped Pulse", "WPOKT": "wrapped POKT", + "WPOR": "Wrapped Portugal National Team", "WPP": "Green Energy Token", "WPR": "WePower", "WQT": "Work Quest", @@ -13108,7 +15085,9 @@ "WSDOGE": "Doge of Woof Street", "WSG": "Wall Street Games", "WSGV1": "Wall Street Games v1", - "WSHIB": "wShiba", + "WSH": "White Yorkshire", + "WSHIB": "Wrapped Shiba Inu (Wormhole)", + "WSHIBA": "wShiba", "WSI": "WeSendit", "WSIENNA": "Sienna ERC20", "WSM": "Wall Street Memes", @@ -13116,12 +15095,14 @@ "WSTA": "Wrapped Statera", "WSTETH": "Lido wstETH", "WSTOR": "StorageChain", + "WSTORV1": "StorageChain v1", "WSTR": "Wrapped Star", "WSTUSDT": "wstUSDT", "WSX": "WeAreSatoshi", "WT": "WeToken", "WTAO": "Wrapped TAO", "WTC": "Waltonchain", + "WTE": "Wonder Energy Technology", "WTF": "Waterfall Governance", "WTFT": "WTF Token", "WTFUEL": "Wrapped TFUEL", @@ -13134,40 +15115,54 @@ "WTT": "Giga Watt", "WTWOOL": "Wolf Town Wool", "WUF": "WUFFI", + "WUK": "WUKONG", + "WUKONG": "Sun Wukong", "WUSD": "Worldwide USD", "WUST": "Wrapped UST Token", + "WVG0": "Wrapped Virgin Gen-0 CryptoKittties", "WVTRS": "Vitreus", "WW3": "WW3", "WWAN": "Wrapped WAN", "WWB": "Wowbit", + "WWBNB": "Wrapped BNB (Wormhole)", "WWD": "Wolf Works DAO", "WWDOGE": "Wrapped WDOGE", "WWEMIX": "WWEMIX", "WWF": "WWF", + "WWMATIC": "Wrapped Polygon (Wormhole)", + "WWRY": "WeWillRugYou", "WWY": "WeWay", "WX": "WX Token", "WXDAI": "Wrapped XDAI", "WXDC": "Wrapped XDC", + "WXM": "WeatherXM", + "WXRP": "Wrapped XRP", "WXT": "WXT", "WXTZ": "Wrapped Tezos", + "WYN": "Wynn", "WYNN": "Anita Max Wynn", "WYS": "Wysker", + "WYZ": "WYZth", "WZEC": "Wrapped Zcash", "WZEDX": "Wrapped Zedxion", "WZENIQ": "Wrapped Zeniq (ETH)", "WZETA": "Wrapped Zeta", "WZM": "Woozoo Music", + "WZNN": "Wrapped Zenon (Zenon Bridge)", + "WZNNV1": "Wrapped Zenon (Zenon Bridge) v1", "WZRD": "Bitcoin Wizards", - "X": "AI-X", + "X": "X Empire", "X2": "X2Coin", "X2Y2": "X2Y2", "X42": "X42 Protocol", + "X7": "X7", "X7C": "X7 Coin", "X7DAO": "X7DAO", "X7R": "X7R", "X8X": "X8Currency", "XACT": "XactToken", "XAEAXII": "XAEA-Xii Token", + "XAGX": "Silver Token", "XAH": "Xahau", "XAI": "Xai", "XALGO": "Wrapped ALGO", @@ -13179,6 +15174,7 @@ "XAS": "Asch", "XAT": "ShareAt", "XAUC": "XauCoin", + "XAUM": "Matrixdock Gold", "XAUR": "Xaurum", "XAUT": "Tether Gold", "XAVA": "Avalaunch", @@ -13219,6 +15215,7 @@ "XCG": "Xchange", "XCH": "Chia", "XCHF": "CryptoFranc", + "XCHNG": "Chainge Finance", "XCI": "Cannabis Industry Coin", "XCLR": "ClearCoin", "XCM": "CoinMetro", @@ -13283,16 +15280,19 @@ "XG": "XG Sports", "XGB": "GoldenBird", "XGC": "Xiglute Coin", + "XGD": "X Gold", "XGEM": "Exchange Genesis Ethlas Medium", "XGLI": "Glitter Finance", "XGOLD": "XGOLD COIN", "XGOX": "Go!", + "XGP": "XGP", "XGPT": "XGPT", "XGR": "GoldReserve", "XGRO": "Growth DeFi", "XGT": "Xion Finance", "XHI": "HiCoin", "XHP": "XHYPE", + "XHPV1": "XHYPE v1", "XHT": "HollaEx", "XHV": "Haven Protocol", "XI": "Xi", @@ -13302,6 +15302,7 @@ "XIDR": "XIDR", "XIL": "Xillion", "XIN": "Mixin", + "XING": "Xing Xing", "XINU": "XINU", "XIO": "Blockzero Labs", "XIOS": "Xios", @@ -13320,6 +15321,7 @@ "XLN": "LunaOne", "XLQ": "Alqo", "XLR": "Solaris", + "XLS": "Elis", "XLT": "Nexalt", "XM": "xMooney", "XMARK": "xMARK", @@ -13338,11 +15340,13 @@ "XMS": "Megastake", "XMT": "MetalSwap", "XMV": "MoneroV", + "XMW": "Morphware", "XMX": "XMax", "XMY": "MyriadCoin", "XNA": "Neurai", "XNB": "Xeonbit", "XNC": "Xenios", + "XNET": "XNET Mobile", "XNFT": "xNFT Protocol", "XNG": "Enigma", "XNK": "Ink Protocol", @@ -13361,6 +15365,7 @@ "XOT": "Okuru", "XOV": "XOVBank", "XOX": "XOX Labs", + "XOXNO": "XOXNO", "XP": "Experience Points", "XPA": "XPA", "XPAT": "Bitnation Pangea", @@ -13408,6 +15413,7 @@ "XRISE": "Xrise", "XRL": "Rialto.AI", "XRLM": "xRealm.ai", + "XROCK": "xRocket", "XROOTAI": "XRootAI", "XRP": "XRP", "XRP2": "XRP2.0", @@ -13416,6 +15422,7 @@ "XRPAYNET": "XRPayNet", "XRPC": "Xrp Classic", "XRPCHAIN": "Ripple Chain", + "XRPEPE": "XRPEPE", "XRPH": "XRP Healthcare", "XRS": "Xrius", "XRT": "Robonomics Network", @@ -13457,7 +15464,9 @@ "XTRA": "ExtraCredit", "XTRACK": "Xtrack AI", "XTREME": "ExtremeCoin", + "XTREMEV": "Xtremeverse", "XTRM": "XTRM COIN", + "XTRUMP": "X TRUMP", "XTT": "XSwap Treasure", "XTTB20": "XTblock", "XTUSD": "XT Stablecoin XTUSD", @@ -13470,6 +15479,7 @@ "XUP": "UPGRADE", "XUPS": "Xups", "XUV": "XUV Coin", + "XV": "XV", "XVC": "Vcash", "XVE": "The Vegan Initiative", "XVG": "Verge", @@ -13488,11 +15498,14 @@ "XYM": "Symbol", "XYO": "XY Oracle", "XYZ": "Universe.XYZ", + "XZK": "Mystiko Network", + "Y24": "Yield 24", "Y2K": "Y2K", "Y8U": "Y8U", "YAC": "YAcCoin", "YACHT": "YachtingVerse", "YAE": "Cryptonovae", + "YAFA": "Free Palestine", "YAG": "Yaki Gold", "YAI": "Ÿ", "YAK": "Yield Yak", @@ -13506,6 +15519,7 @@ "YAP": "Yap Stone", "YARL": "Yarloo", "YAW": "Yawww", + "YAWN": "YAWN", "YAXIS": "yAxis", "YAY": "YAY Games", "YAYCOIN": "YAYcoin", @@ -13519,6 +15533,7 @@ "YDF": "Yieldification", "YDOGE": "Yorkie Doge", "YDR": "YDragon", + "YEARN": "YearnTogether", "YEC": "Ycash", "YEE": "Yeeco", "YEED": "Yggdrash", @@ -13542,7 +15557,8 @@ "YFFI": "yffi finance", "YFFII": "YFFII Finance", "YFI": "yearn.finance", - "YFIE": "YFIEXCHANGE.FINANCE", + "YFIE": "yearn.finance (Avalanche Bridge)", + "YFIEXCHANGE": "YFIEXCHANGE.FINANCE", "YFII": "DFI.money", "YFIII": "Dify.Finance", "YFIVE": "YFIVE FINANCE", @@ -13553,16 +15569,19 @@ "YFV": "YFValue", "YFX": "Your Futures Exchange", "YGG": "Yield Guild Games", + "YIDO": "Yidocy Plus", "YIELD": "Yield Protocol", "YIELDX": "Yield Finance", "YIKES": "Yikes Dog", "YIN": "YIN Finance", "YINBI": "Yinbi", + "YLAY": "Yelay", "YLC": "YoloCash", "YLD": "YIELD App", "YLDY": "Yieldly", "YMC": "YamahaCoin", "YMS": "Yeni Malatyaspor Token", + "YNETH": "YieldNest Restaked ETH", "YO": "Yobit Token", "YOBASE": "All Your Base", "YOC": "YoCoin", @@ -13571,18 +15590,24 @@ "YODE": "YodeSwap", "YOLO": "YoloNolo", "YOM": "YOM", + "YONNY": "YONNY", "YOOSHI": "YooShi", "YOP": "Yield Optimization Platform & Protocol", + "YORAN": "YORAN THE CAVALIER", + "YORI": "YORI", "YOSHI": "Yoshi.exchange", "YOTD": "Year of the Dragon", "YOTO": "yotoshi", "YOU": "YOU Chain", "YOUC": "yOUcash", + "YOUNES": "YOUNES", "YOURAI": "YOUR AI", + "YOURMOM": "YOUR MOM DOG", "YOVI": "YobitVirtualCoin", "YOYOW": "Yoyow", "YPC": "YoungParrot", "YPIE": "PieDAO Yearn Ecosystem Pie", + "YPRISMA": "Yearn yPRISMA", "YSAFE": "yieldfarming.insure", "YSEC": "Yearn Secure", "YSR": "Ystar", @@ -13592,25 +15617,31 @@ "YTS": "YetiSwap", "YU": "BOUNTYKINDS", "YUANG": "Yuang Coin", + "YUCHEN": "Sun Yuchen", "YUCJ": "Yu Coin", "YUCT": "Yucreat", "YUDI": "Yudi", "YUGE": "YUGE COIN", "YUKI": "YUKI", + "YUKIE": "Yukie", "YUKKY": "YUKKY", "YUM": "Yumerium", "YUMMI": "Yummi Universe", "YUMMY": "Yummy", "YUP": "Crowdholding", "YURI": "YURI", + "YUSD": "YUSD Stablecoin", "YUSE": "Yuse Token", "YUSRA": "YUSRA", + "YUSUF": "Yusuf Dikec Meme", "YUZU": "YuzuSwap", "YVBOOST": "Yearn Compounding veCRV yVault", "YVS": "YVS.Finance", + "YVYFI": "YFI yVault", "YYAVAX": "Yield Yak AVAX", "YYE": "YYE Energy", "YYFI": "YYFI.Protocol", + "YYOLO": "yYOLO", "Z3": "Z-Cubed", "ZABAKU": "Zabaku Inu", "ZACK": "Zack Morris", @@ -13623,10 +15654,14 @@ "ZAO": "zkTAO", "ZAP": "Zap", "ZAPI": "Zapicorn", + "ZAPO": "Zapo AI", "ZARP": "ZARP Stablecoin", "ZARX": "eToro South African Rand", "ZASH": "ZIMBOCASH", "ZAT": "ZatGo", + "ZAZA": "ZAZA", + "ZAZU": "Zazu", + "ZAZZLES": "Zazzles", "ZB": "ZB", "ZBC": "Zebec Protocol", "ZBCN": "Zebec Network", @@ -13634,6 +15669,7 @@ "ZBU": "Zeebu", "ZCC": "ZCC Coin", "ZCC1": "ZeroCarbon", + "ZCD": "ZChains", "ZCG": "ZCashGOLD", "ZCHF": "Frankencoin", "ZCHN": "Zichain", @@ -13646,12 +15682,15 @@ "ZCULT": "Zkcult", "ZCX": "Unizen", "ZDAI": "Zydio AI", + "ZDC": "Zodiacs", + "ZDCV2": "ZodiacsV2", "ZDEX": "Zeedex", "ZDR": "Zloadr", "ZEBU": "ZEBU", "ZEC": "ZCash", "ZECD": "ZCashDarkCoin", - "ZED": "ZedCoins", + "ZED": "ZED Token", + "ZEDCOIN": "ZedCoin", "ZEDD": "ZedDex", "ZEDTOKEN": "Zed Token", "ZEDX": "ZEDXION", @@ -13669,11 +15708,14 @@ "ZENI": "Zennies", "ZENIQ": "Zeniq Coin", "ZENITH": "Zenith Chain", + "ZENQ": "Zenqira", "ZENT": "Zentry", "ZEON": "Zeon Network", "ZEP": "Zeppelin Dao", "ZEPH": "Zephyr Protocol", "ZER": "Zero", + "ZEREBRO": "Zerebro", + "ZERO": "ZeroLend", "ZEROB": "ZeroBank", "ZEROEX": "0.exchange", "ZES": "Zetos", @@ -13686,7 +15728,9 @@ "ZETRIX": "Zetrix", "ZEUM": "Colizeum", "ZEUS": "Zeus Network", + "ZEUSPEPES": "Zeus", "ZEXI": "ZEXICON", + "ZEXY": "ZEXY", "ZF": "zkSwap Finance ", "ZFL": "Zuflo Coin", "ZFLOKI": "zkFloki", @@ -13694,6 +15738,7 @@ "ZGD": "ZambesiGold", "ZGEM": "GemSwap", "ZHC": "ZHC : Zero Hour Cash", + "ZHOA": "Chengpang Zhoa", "ZIBU": "Zibu", "ZIG": "Zignaly", "ZIGAP": "ZIGAP", @@ -13719,6 +15764,7 @@ "ZKARCH": "zkArchive", "ZKB": "ZKBase", "ZKBOB": "BOB", + "ZKCRO": "Cronos zkEVM CRO", "ZKDOGE": "zkDoge", "ZKDX": "ZKDX", "ZKE": "zkEra Finance", @@ -13730,6 +15776,7 @@ "ZKID": "zkSync id", "ZKIN": "zkInfra", "ZKJ": "Polyhedra Network", + "ZKL": "zkLink", "ZKLAB": "zkSync Labs", "ZKLK": "ZkLock", "ZKML": "zKML", @@ -13770,6 +15817,7 @@ "ZONX": "METAZONX", "ZOO": "ZooKeeper", "ZOOA": "Zoopia", + "ZOOC": "ZOO Crypto World", "ZOOM": "ZoomCoin", "ZOOMER": "Zoomer Coin", "ZOON": "CryptoZoon", @@ -13777,6 +15825,7 @@ "ZORA": "Zoracles", "ZORKSEES": "Zorksees", "ZORO": "Zoro Inu", + "ZORRO": "Zorro", "ZORT": "Zort", "ZP": "Zen Protocol", "ZPAE": "ZelaaPayAE", @@ -13789,6 +15838,8 @@ "ZPTC": "Zeptacoin", "ZRC": "ZrCoin", "ZRO": "LayerZero", + "ZRPY": "Zerpaay", + "ZRS": "Zaros", "ZRX": "0x", "ZSC": "Zeusshield", "ZSD": "Zephyr Protocol Stable Dollar", @@ -13797,11 +15848,13 @@ "ZT": "ZBG Token", "ZTC": "ZeTo", "ZTG": "Zeitgeist", + "ZTK": "Zefi", "ZTX": "ZTX", "ZUC": "Zeux", "ZUCKPEPE": "ZuckPepe", "ZUKI": "Zuki Moba", "ZUM": "ZumCoin", + "ZUN": "Zunami Governance Token", "ZUNA": "ZUNA", "ZUNUSD": "Zunami USD", "ZUR": "Zurcoin", @@ -13816,8 +15869,11 @@ "ZWAP": "ZilSwap", "ZXC": "Oxcert", "ZXT": "Zcrypt", + "ZYB": "Zyberswap", "ZYD": "ZayedCoin", + "ZYGO": "Zygo the frog", "ZYN": "Zynecoin", + "ZYNC": "ZynCoin", "ZYNE": "Zynergy", "ZYPTO": "Zypto Token", "ZYR": "Zyrri", @@ -13831,5 +15887,6 @@ "gOHM": "Governance OHM", "redBUX": "redBUX", "sOHM": "Staked Olympus", + "vXDEFI": "vXDEFI", "wsOHM": "Wrapped Staked Olympus" } From 2067e8ea403c5487ba8cd7b6652d0177944cbd86 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 30 Nov 2024 11:49:24 +0100 Subject: [PATCH 56/65] Feature/add support for dividends in Ghostfolio data provider (#4081) * Add support for dividends --- .../ghostfolio/get-dividends.dto.ts | 15 ++++++ .../ghostfolio/ghostfolio.controller.ts | 41 +++++++++++++++ .../ghostfolio/ghostfolio.service.ts | 47 ++++++++++++++++- .../ghostfolio/ghostfolio.service.ts | 50 ++++++++++++++++++- .../interfaces/data-provider.interface.ts | 8 ++- .../src/app/pages/api/api-page.component.ts | 21 ++++++++ apps/client/src/app/pages/api/api-page.html | 14 ++++++ libs/common/src/lib/interfaces/index.ts | 2 + .../responses/dividends-response.interface.ts | 7 +++ 9 files changed, 201 insertions(+), 4 deletions(-) create mode 100644 apps/api/src/app/endpoints/data-providers/ghostfolio/get-dividends.dto.ts create mode 100644 libs/common/src/lib/interfaces/responses/dividends-response.interface.ts diff --git a/apps/api/src/app/endpoints/data-providers/ghostfolio/get-dividends.dto.ts b/apps/api/src/app/endpoints/data-providers/ghostfolio/get-dividends.dto.ts new file mode 100644 index 00000000..6df457c6 --- /dev/null +++ b/apps/api/src/app/endpoints/data-providers/ghostfolio/get-dividends.dto.ts @@ -0,0 +1,15 @@ +import { Granularity } from '@ghostfolio/common/types'; + +import { IsIn, IsISO8601, IsOptional } from 'class-validator'; + +export class GetDividendsDto { + @IsISO8601() + from: string; + + @IsIn(['day', 'month'] as Granularity[]) + @IsOptional() + granularity: Granularity; + + @IsISO8601() + to: string; +} diff --git a/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.controller.ts b/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.controller.ts index 58a3224c..3ccef28c 100644 --- a/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.controller.ts +++ b/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.controller.ts @@ -3,6 +3,7 @@ import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard' import { parseDate } from '@ghostfolio/common/helper'; import { DataProviderGhostfolioStatusResponse, + DividendsResponse, HistoricalResponse, LookupResponse, QuotesResponse @@ -23,6 +24,7 @@ import { REQUEST } from '@nestjs/core'; import { AuthGuard } from '@nestjs/passport'; import { getReasonPhrase, StatusCodes } from 'http-status-codes'; +import { GetDividendsDto } from './get-dividends.dto'; import { GetHistoricalDto } from './get-historical.dto'; import { GetQuotesDto } from './get-quotes.dto'; import { GhostfolioService } from './ghostfolio.service'; @@ -34,6 +36,45 @@ export class GhostfolioController { @Inject(REQUEST) private readonly request: RequestWithUser ) {} + @Get('dividends/:symbol') + @HasPermission(permissions.enableDataProviderGhostfolio) + @UseGuards(AuthGuard('jwt'), HasPermissionGuard) + public async getDividends( + @Param('symbol') symbol: string, + @Query() query: GetDividendsDto + ): Promise { + const maxDailyRequests = await this.ghostfolioService.getMaxDailyRequests(); + + if ( + this.request.user.dataProviderGhostfolioDailyRequests > maxDailyRequests + ) { + throw new HttpException( + getReasonPhrase(StatusCodes.TOO_MANY_REQUESTS), + StatusCodes.TOO_MANY_REQUESTS + ); + } + + try { + const dividends = await this.ghostfolioService.getDividends({ + symbol, + from: parseDate(query.from), + granularity: query.granularity, + to: parseDate(query.to) + }); + + await this.ghostfolioService.incrementDailyRequests({ + userId: this.request.user.id + }); + + return dividends; + } catch { + throw new HttpException( + getReasonPhrase(StatusCodes.INTERNAL_SERVER_ERROR), + StatusCodes.INTERNAL_SERVER_ERROR + ); + } + } + @Get('historical/:symbol') @HasPermission(permissions.enableDataProviderGhostfolio) @UseGuards(AuthGuard('jwt'), HasPermissionGuard) diff --git a/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.service.ts b/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.service.ts index 52baa10d..875a13c9 100644 --- a/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.service.ts +++ b/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.service.ts @@ -1,6 +1,7 @@ import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service'; import { + GetDividendsParams, GetHistoricalParams, GetQuotesParams, GetSearchParams @@ -15,6 +16,7 @@ import { import { PROPERTY_DATA_SOURCES_GHOSTFOLIO_DATA_PROVIDER_MAX_REQUESTS } from '@ghostfolio/common/config'; import { DataProviderInfo, + DividendsResponse, HistoricalResponse, LookupItem, LookupResponse, @@ -34,6 +36,48 @@ export class GhostfolioService { private readonly propertyService: PropertyService ) {} + public async getDividends({ + from, + granularity, + requestTimeout = this.configurationService.get('REQUEST_TIMEOUT'), + symbol, + to + }: GetDividendsParams) { + const result: DividendsResponse = { dividends: {} }; + + try { + const promises: Promise<{ + [date: string]: IDataProviderHistoricalResponse; + }>[] = []; + + for (const dataProviderService of this.getDataProviderServices()) { + promises.push( + dataProviderService + .getDividends({ + from, + granularity, + requestTimeout, + symbol, + to + }) + .then((dividends) => { + result.dividends = dividends; + + return dividends; + }) + ); + } + + await Promise.all(promises); + + return result; + } catch (error) { + Logger.error(error, 'GhostfolioService'); + + throw error; + } + } + public async getHistorical({ from, granularity, @@ -86,10 +130,11 @@ export class GhostfolioService { } public async getQuotes({ requestTimeout, symbols }: GetQuotesParams) { - const promises: Promise[] = []; const results: QuotesResponse = { quotes: {} }; try { + const promises: Promise[] = []; + for (const dataProvider of this.getDataProviderServices()) { const maximumNumberOfSymbolsPerRequest = dataProvider.getMaxNumberOfSymbolsPerRequest?.() ?? diff --git a/apps/api/src/services/data-provider/ghostfolio/ghostfolio.service.ts b/apps/api/src/services/data-provider/ghostfolio/ghostfolio.service.ts index 5be7d831..25ffdc67 100644 --- a/apps/api/src/services/data-provider/ghostfolio/ghostfolio.service.ts +++ b/apps/api/src/services/data-provider/ghostfolio/ghostfolio.service.ts @@ -19,6 +19,7 @@ import { import { DATE_FORMAT } from '@ghostfolio/common/helper'; import { DataProviderInfo, + DividendsResponse, HistoricalResponse, LookupResponse, QuotesResponse @@ -71,8 +72,53 @@ export class GhostfolioService implements DataProviderInterface { }; } - public async getDividends({}: GetDividendsParams) { - return {}; + public async getDividends({ + from, + granularity = 'day', + requestTimeout = this.configurationService.get('REQUEST_TIMEOUT'), + symbol, + to + }: GetDividendsParams): Promise<{ + [date: string]: IDataProviderHistoricalResponse; + }> { + let response: { + [date: string]: IDataProviderHistoricalResponse; + } = {}; + + try { + const abortController = new AbortController(); + + setTimeout(() => { + abortController.abort(); + }, requestTimeout); + + const { dividends } = await got( + `${this.URL}/v1/data-providers/ghostfolio/dividends/${symbol}?from=${format(from, DATE_FORMAT)}&granularity=${granularity}&to=${format( + to, + DATE_FORMAT + )}`, + { + headers: await this.getRequestHeaders(), + // @ts-ignore + signal: abortController.signal + } + ).json(); + + response = dividends; + } catch (error) { + let message = error; + + if (error.response?.statusCode === StatusCodes.TOO_MANY_REQUESTS) { + message = 'RequestError: The daily request limit has been exceeded'; + } else if (error.response?.statusCode === StatusCodes.UNAUTHORIZED) { + message = + 'RequestError: The provided API key is invalid. Please update it in the Settings section of the Admin Control panel.'; + } + + Logger.error(message, 'GhostfolioService'); + } + + return response; } public async getHistorical({ diff --git a/apps/api/src/services/data-provider/interfaces/data-provider.interface.ts b/apps/api/src/services/data-provider/interfaces/data-provider.interface.ts index 7352ce78..e448b4e1 100644 --- a/apps/api/src/services/data-provider/interfaces/data-provider.interface.ts +++ b/apps/api/src/services/data-provider/interfaces/data-provider.interface.ts @@ -21,7 +21,13 @@ export interface DataProviderInterface { getDataProviderInfo(): DataProviderInfo; - getDividends({ from, granularity, symbol, to }: GetDividendsParams): Promise<{ + getDividends({ + from, + granularity, + requestTimeout, + symbol, + to + }: GetDividendsParams): Promise<{ [date: string]: IDataProviderHistoricalResponse; }>; diff --git a/apps/client/src/app/pages/api/api-page.component.ts b/apps/client/src/app/pages/api/api-page.component.ts index 7b2d70ae..aa176c0f 100644 --- a/apps/client/src/app/pages/api/api-page.component.ts +++ b/apps/client/src/app/pages/api/api-page.component.ts @@ -1,6 +1,7 @@ import { DATE_FORMAT } from '@ghostfolio/common/helper'; import { DataProviderGhostfolioStatusResponse, + DividendsResponse, HistoricalResponse, LookupResponse, QuotesResponse @@ -21,6 +22,7 @@ import { map, Observable, Subject, takeUntil } from 'rxjs'; templateUrl: './api-page.html' }) export class GfApiPageComponent implements OnInit { + public dividends$: Observable; public historicalData$: Observable; public quotes$: Observable; public status$: Observable; @@ -31,6 +33,7 @@ export class GfApiPageComponent implements OnInit { public constructor(private http: HttpClient) {} public ngOnInit() { + this.dividends$ = this.fetchDividends({ symbol: 'KO' }); this.historicalData$ = this.fetchHistoricalData({ symbol: 'AAPL.US' }); this.quotes$ = this.fetchQuotes({ symbols: ['AAPL.US', 'VOO.US'] }); this.status$ = this.fetchStatus(); @@ -42,6 +45,24 @@ export class GfApiPageComponent implements OnInit { this.unsubscribeSubject.complete(); } + private fetchDividends({ symbol }: { symbol: string }) { + const params = new HttpParams() + .set('from', format(startOfYear(new Date()), DATE_FORMAT)) + .set('to', format(new Date(), DATE_FORMAT)); + + return this.http + .get( + `/api/v1/data-providers/ghostfolio/dividends/${symbol}`, + { params } + ) + .pipe( + map(({ dividends }) => { + return dividends; + }), + takeUntil(this.unsubscribeSubject) + ); + } + private fetchHistoricalData({ symbol }: { symbol: string }) { const params = new HttpParams() .set('from', format(startOfYear(new Date()), DATE_FORMAT)) diff --git a/apps/client/src/app/pages/api/api-page.html b/apps/client/src/app/pages/api/api-page.html index d7dca7fe..a1f286c0 100644 --- a/apps/client/src/app/pages/api/api-page.html +++ b/apps/client/src/app/pages/api/api-page.html @@ -45,4 +45,18 @@ }
    +
    +

    Dividends

    + @if (dividends$) { + @let dividends = dividends$ | async; +
      + @for (dividend of dividends | keyvalue; track dividend) { +
    • + {{ dividend.key }}: + {{ dividend.value.marketPrice }} +
    • + } +
    + } +
    diff --git a/libs/common/src/lib/interfaces/index.ts b/libs/common/src/lib/interfaces/index.ts index 38093794..4d5ce66d 100644 --- a/libs/common/src/lib/interfaces/index.ts +++ b/libs/common/src/lib/interfaces/index.ts @@ -41,6 +41,7 @@ import type { Product } from './product'; import type { AccountBalancesResponse } from './responses/account-balances-response.interface'; import type { BenchmarkResponse } from './responses/benchmark-response.interface'; import type { DataProviderGhostfolioStatusResponse } from './responses/data-provider-ghostfolio-status-response.interface'; +import type { DividendsResponse } from './responses/dividends-response.interface'; import type { ResponseError } from './responses/errors.interface'; import type { HistoricalResponse } from './responses/historical-response.interface'; import type { ImportResponse } from './responses/import-response.interface'; @@ -79,6 +80,7 @@ export { Coupon, DataProviderGhostfolioStatusResponse, DataProviderInfo, + DividendsResponse, EnhancedSymbolProfile, Export, Filter, diff --git a/libs/common/src/lib/interfaces/responses/dividends-response.interface.ts b/libs/common/src/lib/interfaces/responses/dividends-response.interface.ts new file mode 100644 index 00000000..f7cacf89 --- /dev/null +++ b/libs/common/src/lib/interfaces/responses/dividends-response.interface.ts @@ -0,0 +1,7 @@ +import { IDataProviderHistoricalResponse } from '@ghostfolio/api/services/interfaces/interfaces'; + +export interface DividendsResponse { + dividends: { + [date: string]: IDataProviderHistoricalResponse; + }; +} From d8ab48efc2ee416d522790560716cb206288c36b Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 30 Nov 2024 11:52:13 +0100 Subject: [PATCH 57/65] Feature/update OSS friends (#4078) * Update OSS friends --- apps/client/src/assets/oss-friends.json | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/apps/client/src/assets/oss-friends.json b/apps/client/src/assets/oss-friends.json index 0fe72651..c3a12793 100644 --- a/apps/client/src/assets/oss-friends.json +++ b/apps/client/src/assets/oss-friends.json @@ -1,5 +1,5 @@ { - "createdAt": "2024-08-31T00:00:00.000Z", + "createdAt": "2024-11-27T00:00:00.000Z", "data": [ { "name": "Aptabase", @@ -53,7 +53,7 @@ }, { "name": "Formbricks", - "description": "Survey granular user segments at any point in the user journey. Gather up to 6x more insights with targeted micro-surveys. All open-source.", + "description": "Open source survey software and Experience Management Platform. Understand your customers, keep full control over your data.", "href": "https://formbricks.com" }, { @@ -81,6 +81,11 @@ "description": "Open source, end-to-end encrypted platform that lets you securely manage secrets and configs across your team, devices, and infrastructure.", "href": "https://infisical.com" }, + { + "name": "KeepHQ", + "description": "Keep is an open-source AIOps (AI for IT operations) platform", + "href": "https://www.keephq.dev" + }, { "name": "Langfuse", "description": "Open source LLM engineering platform. Debug, analyze and iterate together.", From 11a32a75c684561b0ddca7e64ce25f40fde21d49 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 30 Nov 2024 17:27:47 +0100 Subject: [PATCH 58/65] Feature/upgrade prisma to version 6 (#4082) * Upgrade prisma to version 6 * Update changelog --- CHANGELOG.md | 1 + package-lock.json | 68 +++++++++++++++++++++++------------------------ package.json | 4 +-- 3 files changed, 37 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d9ac3d3..8987979e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Refreshed the cryptocurrencies list - Increased the default request timeout (`REQUEST_TIMEOUT`) - Upgraded `cheerio` from version `1.0.0-rc.12` to `1.0.0` +- Upgraded `prisma` from version `5.22.0` to `6.0.0` ## 2.124.1 - 2024-11-25 diff --git a/package-lock.json b/package-lock.json index 6066d16e..49e3f481 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,7 +40,7 @@ "@nestjs/platform-express": "10.1.3", "@nestjs/schedule": "3.0.2", "@nestjs/serve-static": "4.0.0", - "@prisma/client": "5.22.0", + "@prisma/client": "6.0.0", "@simplewebauthn/browser": "9.0.1", "@simplewebauthn/server": "9.0.3", "@stripe/stripe-js": "4.9.0", @@ -150,7 +150,7 @@ "nx": "20.1.2", "prettier": "3.3.3", "prettier-plugin-organize-attributes": "1.0.0", - "prisma": "5.22.0", + "prisma": "6.0.0", "react": "18.2.0", "react-dom": "18.2.0", "replace-in-file": "7.0.1", @@ -8352,13 +8352,13 @@ "license": "MIT" }, "node_modules/@prisma/client": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.22.0.tgz", - "integrity": "sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.0.0.tgz", + "integrity": "sha512-tOBhG35ozqZ/5Y6B0TNOa6cwULUW8ijXqBXcgb12bfozqf6eGMyGs+jphywCsj6uojv5lAZZnxVSoLMVebIP+g==", "hasInstallScript": true, "license": "Apache-2.0", "engines": { - "node": ">=16.13" + "node": ">=18.18" }, "peerDependencies": { "prisma": "*" @@ -8370,53 +8370,53 @@ } }, "node_modules/@prisma/debug": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.22.0.tgz", - "integrity": "sha512-AUt44v3YJeggO2ZU5BkXI7M4hu9BF2zzH2iF2V5pyXT/lRTyWiElZ7It+bRH1EshoMRxHgpYg4VB6rCM+mG5jQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.0.0.tgz", + "integrity": "sha512-eUjoNThlDXdyJ1iQ2d7U6aTVwm59EwvODb5zFVNJEokNoSiQmiYWNzZIwZyDmZ+j51j42/0iTaHIJ4/aZPKFRg==", "devOptional": true, "license": "Apache-2.0" }, "node_modules/@prisma/engines": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.22.0.tgz", - "integrity": "sha512-UNjfslWhAt06kVL3CjkuYpHAWSO6L4kDCVPegV6itt7nD1kSJavd3vhgAEhjglLJJKEdJ7oIqDJ+yHk6qO8gPA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.0.0.tgz", + "integrity": "sha512-ZZCVP3q22ifN6Ex6C8RIcTDBlRtMJS2H1ljV0knCiWNGArvvkEbE88W3uDdq/l4+UvyvHpGzdf9ZsCWSQR7ZQQ==", "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "5.22.0", - "@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", - "@prisma/fetch-engine": "5.22.0", - "@prisma/get-platform": "5.22.0" + "@prisma/debug": "6.0.0", + "@prisma/engines-version": "5.23.0-27.5dbef10bdbfb579e07d35cc85fb1518d357cb99e", + "@prisma/fetch-engine": "6.0.0", + "@prisma/get-platform": "6.0.0" } }, "node_modules/@prisma/engines-version": { - "version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2.tgz", - "integrity": "sha512-2PTmxFR2yHW/eB3uqWtcgRcgAbG1rwG9ZriSvQw+nnb7c4uCr3RAcGMb6/zfE88SKlC1Nj2ziUvc96Z379mHgQ==", + "version": "5.23.0-27.5dbef10bdbfb579e07d35cc85fb1518d357cb99e", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.23.0-27.5dbef10bdbfb579e07d35cc85fb1518d357cb99e.tgz", + "integrity": "sha512-JmIds0Q2/vsOmnuTJYxY4LE+sajqjYKhLtdOT6y4imojqv5d/aeVEfbBGC74t8Be1uSp0OP8lxIj2OqoKbLsfQ==", "devOptional": true, "license": "Apache-2.0" }, "node_modules/@prisma/fetch-engine": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.22.0.tgz", - "integrity": "sha512-bkrD/Mc2fSvkQBV5EpoFcZ87AvOgDxbG99488a5cexp5Ccny+UM6MAe/UFkUC0wLYD9+9befNOqGiIJhhq+HbA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.0.0.tgz", + "integrity": "sha512-j2m+iO5RDPRI7SUc7sHo8wX7SA4iTkJ+18Sxch8KinQM46YiCQD1iXKN6qU79C1Fliw5Bw/qDyTHaTsa3JMerA==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "5.22.0", - "@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", - "@prisma/get-platform": "5.22.0" + "@prisma/debug": "6.0.0", + "@prisma/engines-version": "5.23.0-27.5dbef10bdbfb579e07d35cc85fb1518d357cb99e", + "@prisma/get-platform": "6.0.0" } }, "node_modules/@prisma/get-platform": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.22.0.tgz", - "integrity": "sha512-pHhpQdr1UPFpt+zFfnPazhulaZYCUqeIcPpJViYoq9R+D/yw4fjE+CtnsnKzPYm0ddUbeXUzjGVGIRVgPDCk4Q==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.0.0.tgz", + "integrity": "sha512-PS6nYyIm9g8C03E4y7LknOfdCw/t2KyEJxntMPQHQZCOUgOpF82Ma60mdlOD08w90I3fjLiZZ0+MadenR3naDQ==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "5.22.0" + "@prisma/debug": "6.0.0" } }, "node_modules/@redis/bloom": { @@ -29489,20 +29489,20 @@ } }, "node_modules/prisma": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.22.0.tgz", - "integrity": "sha512-vtpjW3XuYCSnMsNVBjLMNkTj6OZbudcPPTPYHqX0CJfpcdWciI1dM8uHETwmDxxiqEwCIE6WvXucWUetJgfu/A==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.0.0.tgz", + "integrity": "sha512-RX7KtbW7IoEByf7MR32JK1PkVYLVYFqeODTtiIX3cqekq1aKdsF3Eud4zp2sUShMLjvdb5Jow0LbUjRq5LVxPw==", "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@prisma/engines": "5.22.0" + "@prisma/engines": "6.0.0" }, "bin": { "prisma": "build/index.js" }, "engines": { - "node": ">=16.13" + "node": ">=18.18" }, "optionalDependencies": { "fsevents": "2.3.3" diff --git a/package.json b/package.json index 8d9624de..a2d78c05 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,7 @@ "@nestjs/platform-express": "10.1.3", "@nestjs/schedule": "3.0.2", "@nestjs/serve-static": "4.0.0", - "@prisma/client": "5.22.0", + "@prisma/client": "6.0.0", "@simplewebauthn/browser": "9.0.1", "@simplewebauthn/server": "9.0.3", "@stripe/stripe-js": "4.9.0", @@ -196,7 +196,7 @@ "nx": "20.1.2", "prettier": "3.3.3", "prettier-plugin-organize-attributes": "1.0.0", - "prisma": "5.22.0", + "prisma": "6.0.0", "react": "18.2.0", "react-dom": "18.2.0", "replace-in-file": "7.0.1", From 3194ed2145fb4dbdc4f1dc94841a9e4ce7516cbe Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 30 Nov 2024 17:30:49 +0100 Subject: [PATCH 59/65] Feature/improve usability of Ghostfolio data provider (#4086) * Improve usability --- .../ghostfolio/ghostfolio.controller.ts | 5 +--- .../ghostfolio/ghostfolio.service.ts | 9 ++++++ .../admin-settings.component.html | 30 +++++++++++++++---- .../admin-settings.component.ts | 6 ++++ .../admin-settings/admin-settings.module.ts | 2 ++ ...er-ghostfolio-status-response.interface.ts | 3 ++ 6 files changed, 46 insertions(+), 9 deletions(-) diff --git a/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.controller.ts b/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.controller.ts index 3ccef28c..788cfd1b 100644 --- a/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.controller.ts +++ b/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.controller.ts @@ -191,9 +191,6 @@ export class GhostfolioController { @HasPermission(permissions.enableDataProviderGhostfolio) @UseGuards(AuthGuard('jwt'), HasPermissionGuard) public async getStatus(): Promise { - return { - dailyRequests: this.request.user.dataProviderGhostfolioDailyRequests, - dailyRequestsMax: await this.ghostfolioService.getMaxDailyRequests() - }; + return this.ghostfolioService.getStatus({ user: this.request.user }); } } diff --git a/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.service.ts b/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.service.ts index 875a13c9..7858e24f 100644 --- a/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.service.ts +++ b/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.service.ts @@ -22,6 +22,7 @@ import { LookupResponse, QuotesResponse } from '@ghostfolio/common/interfaces'; +import { UserWithSettings } from '@ghostfolio/common/types'; import { Injectable, Logger } from '@nestjs/common'; import { DataSource } from '@prisma/client'; @@ -208,6 +209,14 @@ export class GhostfolioService { } } + public async getStatus({ user }: { user: UserWithSettings }) { + return { + dailyRequests: user.dataProviderGhostfolioDailyRequests, + dailyRequestsMax: await this.getMaxDailyRequests(), + subscription: user.subscription + }; + } + public async incrementDailyRequests({ userId }: { userId: string }) { await this.prismaService.analytics.update({ data: { diff --git a/apps/client/src/app/components/admin-settings/admin-settings.component.html b/apps/client/src/app/components/admin-settings/admin-settings.component.html index 35ed556b..f9a6084c 100644 --- a/apps/client/src/app/components/admin-settings/admin-settings.component.html +++ b/apps/client/src/app/components/admin-settings/admin-settings.component.html @@ -20,23 +20,43 @@ [enableLink]="false" /> + @if (isGhostfolioApiKeyValid === true) { +
    + + Valid until + {{ + ghostfolioApiStatus?.subscription?.expiresAt + | date: defaultDateFormat + }} +
    + }
    @if (isGhostfolioApiKeyValid === true) {
    -
    +
    {{ ghostfolioApiStatus.dailyRequests }} of {{ ghostfolioApiStatus.dailyRequestsMax }} daily requests
    + + +
    } @else if (isGhostfolioApiKeyValid === false) {
    + + + @if (isLoading) { ('/api/v1/tag'); } - public fetchUsers() { + public fetchUsers({ + skip, + take = DEFAULT_PAGE_SIZE + }: { + skip?: number; + take?: number; + }) { let params = new HttpParams(); - params = params.append('take', 30); + params = params.append('skip', skip); + params = params.append('take', take); return this.http.get('/api/v1/admin/user', { params }); } From 11d5f36c31debdc6138b5ae50e26f7390846c025 Mon Sep 17 00:00:00 2001 From: Amandee Ellawala <47607256+amandee27@users.noreply.github.com> Date: Wed, 4 Dec 2024 18:50:22 +0000 Subject: [PATCH 65/65] Feature/extract historical market data editor to reusable component (#4080) * Extract historical market data editor to reusable component * Update changelog --- CHANGELOG.md | 4 + .../admin-market-data-detail.module.ts | 15 -- .../market-data-detail-dialog.module.ts | 26 ---- .../asset-profile-dialog.component.scss | 4 + .../asset-profile-dialog.component.ts | 76 ++++------ .../asset-profile-dialog.html | 48 ++---- .../asset-profile-dialog.module.ts | 6 +- ...cal-market-data-editor-dialog.component.ts | 42 ++++-- .../historical-market-data-editor-dialog.html | 0 .../historical-market-data-editor-dialog.scss | 0 .../interfaces/interfaces.ts | 2 +- ...storical-market-data-editor.component.html | 48 ++++-- ...storical-market-data-editor.component.scss | 4 - ...historical-market-data-editor.component.ts | 138 ++++++++++++++---- .../historical-market-data-editor/index.ts | 1 + 15 files changed, 234 insertions(+), 180 deletions(-) delete mode 100644 apps/client/src/app/components/admin-market-data-detail/admin-market-data-detail.module.ts delete mode 100644 apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.module.ts rename apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.component.ts => libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.component.ts (60%) rename apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.html => libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html (100%) rename apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.scss => libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.scss (100%) rename {apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog => libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog}/interfaces/interfaces.ts (79%) rename apps/client/src/app/components/admin-market-data-detail/admin-market-data-detail.component.html => libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html (51%) rename apps/client/src/app/components/admin-market-data-detail/admin-market-data-detail.component.scss => libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.scss (90%) rename apps/client/src/app/components/admin-market-data-detail/admin-market-data-detail.component.ts => libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts (55%) create mode 100644 libs/ui/src/lib/historical-market-data-editor/index.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index aed13fe0..8e7a0830 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added pagination to the users table of the admin control panel +### Changed + +- Extracted the historical market data editor to a reusable component + ## 2.125.0 - 2024-11-30 ### Changed diff --git a/apps/client/src/app/components/admin-market-data-detail/admin-market-data-detail.module.ts b/apps/client/src/app/components/admin-market-data-detail/admin-market-data-detail.module.ts deleted file mode 100644 index 9f4e1b3b..00000000 --- a/apps/client/src/app/components/admin-market-data-detail/admin-market-data-detail.module.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { GfLineChartComponent } from '@ghostfolio/ui/line-chart'; - -import { CommonModule } from '@angular/common'; -import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; - -import { AdminMarketDataDetailComponent } from './admin-market-data-detail.component'; -import { GfMarketDataDetailDialogModule } from './market-data-detail-dialog/market-data-detail-dialog.module'; - -@NgModule({ - declarations: [AdminMarketDataDetailComponent], - exports: [AdminMarketDataDetailComponent], - imports: [CommonModule, GfLineChartComponent, GfMarketDataDetailDialogModule], - schemas: [CUSTOM_ELEMENTS_SCHEMA] -}) -export class GfAdminMarketDataDetailModule {} diff --git a/apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.module.ts b/apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.module.ts deleted file mode 100644 index f3b55d71..00000000 --- a/apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.module.ts +++ /dev/null @@ -1,26 +0,0 @@ -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 { MatDatepickerModule } from '@angular/material/datepicker'; -import { MatDialogModule } from '@angular/material/dialog'; -import { MatFormFieldModule } from '@angular/material/form-field'; -import { MatInputModule } from '@angular/material/input'; - -import { MarketDataDetailDialog } from './market-data-detail-dialog.component'; - -@NgModule({ - declarations: [MarketDataDetailDialog], - imports: [ - CommonModule, - FormsModule, - MatButtonModule, - MatDatepickerModule, - MatDialogModule, - MatFormFieldModule, - MatInputModule, - ReactiveFormsModule - ], - schemas: [CUSTOM_ELEMENTS_SCHEMA] -}) -export class GfMarketDataDetailDialogModule {} diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.scss b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.scss index b63df013..7057aad8 100644 --- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.scss +++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.scss @@ -3,5 +3,9 @@ .mat-mdc-dialog-content { max-height: unset; + + gf-line-chart { + aspect-ratio: 16/9; + } } } diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts index aacf387e..4fdc2298 100644 --- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts +++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts @@ -1,15 +1,17 @@ import { UpdateAssetProfileDto } from '@ghostfolio/api/app/admin/update-asset-profile.dto'; -import { UpdateMarketDataDto } from '@ghostfolio/api/app/admin/update-market-data.dto'; import { AdminMarketDataService } from '@ghostfolio/client/components/admin-market-data/admin-market-data.service'; import { NotificationService } from '@ghostfolio/client/core/notification/notification.service'; import { AdminService } from '@ghostfolio/client/services/admin.service'; import { DataService } from '@ghostfolio/client/services/data.service'; +import { UserService } from '@ghostfolio/client/services/user/user.service'; import { validateObjectForForm } from '@ghostfolio/client/util/form.util'; import { ghostfolioScraperApiSymbolPrefix } from '@ghostfolio/common/config'; import { DATE_FORMAT } from '@ghostfolio/common/helper'; import { AdminMarketDataDetails, - AssetProfileIdentifier + AssetProfileIdentifier, + LineChartItem, + User } from '@ghostfolio/common/interfaces'; import { translate } from '@ghostfolio/ui/i18n'; @@ -23,7 +25,6 @@ import { } from '@angular/core'; import { FormBuilder, FormControl, Validators } from '@angular/forms'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; -import { MatSnackBar } from '@angular/material/snack-bar'; import { AssetClass, AssetSubClass, @@ -31,7 +32,6 @@ import { SymbolProfile } from '@prisma/client'; import { format } from 'date-fns'; -import { parse as csvToJson } from 'papaparse'; import { EMPTY, Subject } from 'rxjs'; import { catchError, takeUntil } from 'rxjs/operators'; @@ -75,11 +75,13 @@ export class AssetProfileDialog implements OnDestroy, OnInit { }; public currencies: string[] = []; public ghostfolioScraperApiSymbolPrefix = ghostfolioScraperApiSymbolPrefix; + public historicalDataItems: LineChartItem[]; public isBenchmark = false; - public marketDataDetails: MarketData[] = []; + public marketDataItems: MarketData[] = []; public sectors: { [name: string]: { name: string; value: number }; }; + public user: User; private static readonly HISTORICAL_DATA_TEMPLATE = `date;marketPrice\n${format( new Date(), @@ -96,7 +98,7 @@ export class AssetProfileDialog implements OnDestroy, OnInit { public dialogRef: MatDialogRef, private formBuilder: FormBuilder, private notificationService: NotificationService, - private snackBar: MatSnackBar + private userService: UserService ) {} public ngOnInit() { @@ -109,6 +111,16 @@ export class AssetProfileDialog implements OnDestroy, OnInit { } public initialize() { + this.historicalDataItems = undefined; + + this.userService.stateChanged + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe((state) => { + if (state?.user) { + this.user = state.user; + } + }); + this.adminService .fetchAdminMarketDataBySymbol({ dataSource: this.data.dataSource, @@ -121,10 +133,19 @@ export class AssetProfileDialog implements OnDestroy, OnInit { this.assetProfileClass = translate(this.assetProfile?.assetClass); this.assetProfileSubClass = translate(this.assetProfile?.assetSubClass); this.countries = {}; + this.isBenchmark = this.benchmarks.some(({ id }) => { return id === this.assetProfile.id; }); - this.marketDataDetails = marketData; + + this.historicalDataItems = marketData.map(({ date, marketPrice }) => { + return { + date: format(date, DATE_FORMAT), + value: marketPrice + }; + }); + + this.marketDataItems = marketData; this.sectors = {}; if (this.assetProfile?.countries?.length > 0) { @@ -200,47 +221,6 @@ export class AssetProfileDialog implements OnDestroy, OnInit { .subscribe(); } - public onImportHistoricalData() { - try { - const marketData = csvToJson( - this.assetProfileForm.controls['historicalData'].controls['csvString'] - .value, - { - dynamicTyping: true, - header: true, - skipEmptyLines: true - } - ).data as UpdateMarketDataDto[]; - - this.adminService - .postMarketData({ - dataSource: this.data.dataSource, - marketData: { - marketData - }, - symbol: this.data.symbol - }) - .pipe( - catchError(({ error, message }) => { - this.snackBar.open(`${error}: ${message[0]}`, undefined, { - duration: 3000 - }); - return EMPTY; - }), - takeUntil(this.unsubscribeSubject) - ) - .subscribe(() => { - this.initialize(); - }); - } catch { - this.snackBar.open( - $localize`Oops! Could not parse historical data.`, - undefined, - { duration: 3000 } - ); - } - } - public onMarketDataChanged(withRefresh: boolean = false) { if (withRefresh) { this.initialize(); diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html index a5d2205d..eeb43e93 100644 --- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html +++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html @@ -68,50 +68,28 @@
    - + -
    - - - Historical Data (CSV) - - - -
    - -
    - -
    -
    (); public constructor( private adminService: AdminService, private changeDetectorRef: ChangeDetectorRef, - @Inject(MAT_DIALOG_DATA) public data: MarketDataDetailDialogParams, + @Inject(MAT_DIALOG_DATA) + public data: HistoricalMarketDataEditorDialogParams, private dateAdapter: DateAdapter, - public dialogRef: MatDialogRef, + public dialogRef: MatDialogRef, @Inject(MAT_DATE_LOCALE) private locale: string ) {} diff --git a/apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.html b/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html similarity index 100% rename from apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.html rename to libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.html diff --git a/apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.scss b/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.scss similarity index 100% rename from apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/market-data-detail-dialog.scss rename to libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.scss diff --git a/apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/interfaces/interfaces.ts b/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/interfaces/interfaces.ts similarity index 79% rename from apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/interfaces/interfaces.ts rename to libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/interfaces/interfaces.ts index 81188cd1..4248b3fd 100644 --- a/apps/client/src/app/components/admin-market-data-detail/market-data-detail-dialog/interfaces/interfaces.ts +++ b/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/interfaces/interfaces.ts @@ -2,7 +2,7 @@ import { User } from '@ghostfolio/common/interfaces'; import { DataSource } from '@prisma/client'; -export interface MarketDataDetailDialogParams { +export interface HistoricalMarketDataEditorDialogParams { currency: string; dataSource: DataSource; dateString: string; diff --git a/apps/client/src/app/components/admin-market-data-detail/admin-market-data-detail.component.html b/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html similarity index 51% rename from apps/client/src/app/components/admin-market-data-detail/admin-market-data-detail.component.html rename to libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html index 617dd696..b35e1d81 100644 --- a/apps/client/src/app/components/admin-market-data-detail/admin-market-data-detail.component.html +++ b/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.html @@ -1,14 +1,4 @@
    - @for (itemByMonth of marketDataByMonth | keyvalue; track itemByMonth) {
    {{ itemByMonth.key }}
    @@ -43,4 +33,42 @@
    } +
    +
    + + + Historical Data (CSV) + + + +
    + +
    + +
    +
    diff --git a/apps/client/src/app/components/admin-market-data-detail/admin-market-data-detail.component.scss b/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.scss similarity index 90% rename from apps/client/src/app/components/admin-market-data-detail/admin-market-data-detail.component.scss rename to libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.scss index a0353358..cc835a90 100644 --- a/apps/client/src/app/components/admin-market-data-detail/admin-market-data-detail.component.scss +++ b/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.scss @@ -2,10 +2,6 @@ display: block; font-size: 0.9rem; - gf-line-chart { - aspect-ratio: 16/9; - } - .date { font-feature-settings: 'tnum'; font-variant-numeric: tabular-nums; diff --git a/apps/client/src/app/components/admin-market-data-detail/admin-market-data-detail.component.ts b/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts similarity index 55% rename from apps/client/src/app/components/admin-market-data-detail/admin-market-data-detail.component.ts rename to libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts index 1742d830..0fce7862 100644 --- a/apps/client/src/app/components/admin-market-data-detail/admin-market-data-detail.component.ts +++ b/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts @@ -1,4 +1,5 @@ -import { UserService } from '@ghostfolio/client/services/user/user.service'; +import { UpdateMarketDataDto } from '@ghostfolio/api/app/admin/update-market-data.dto'; +import { AdminService } from '@ghostfolio/client/services/admin.service'; import { DATE_FORMAT, getDateFormatString, @@ -6,15 +7,22 @@ import { } from '@ghostfolio/common/helper'; import { LineChartItem, User } from '@ghostfolio/common/interfaces'; +import { CommonModule } from '@angular/common'; import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, + OnDestroy, + OnInit, Output } from '@angular/core'; +import { FormBuilder, ReactiveFormsModule } from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; import { MatDialog } from '@angular/material/dialog'; +import { MatInputModule } from '@angular/material/input'; +import { MatSnackBar } from '@angular/material/snack-bar'; import { DataSource, MarketData } from '@prisma/client'; import { addDays, @@ -29,55 +37,70 @@ import { parseISO } from 'date-fns'; import { first, last } from 'lodash'; +import ms from 'ms'; import { DeviceDetectorService } from 'ngx-device-detector'; -import { Subject, takeUntil } from 'rxjs'; +import { parse as csvToJson } from 'papaparse'; +import { EMPTY, Subject, takeUntil } from 'rxjs'; +import { catchError } from 'rxjs/operators'; -import { MarketDataDetailDialogParams } from './market-data-detail-dialog/interfaces/interfaces'; -import { MarketDataDetailDialog } from './market-data-detail-dialog/market-data-detail-dialog.component'; +import { GfHistoricalMarketDataEditorDialogComponent } from './historical-market-data-editor-dialog/historical-market-data-editor-dialog.component'; +import { HistoricalMarketDataEditorDialogParams } from './historical-market-data-editor-dialog/interfaces/interfaces'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, - selector: 'gf-admin-market-data-detail', - styleUrls: ['./admin-market-data-detail.component.scss'], - templateUrl: './admin-market-data-detail.component.html' + imports: [CommonModule, MatButtonModule, MatInputModule, ReactiveFormsModule], + selector: 'gf-historical-market-data-editor', + standalone: true, + styleUrls: ['./historical-market-data-editor.component.scss'], + templateUrl: './historical-market-data-editor.component.html' }) -export class AdminMarketDataDetailComponent implements OnChanges { +export class GfHistoricalMarketDataEditorComponent + implements OnChanges, OnDestroy, OnInit +{ @Input() currency: string; @Input() dataSource: DataSource; @Input() dateOfFirstActivity: string; @Input() locale = getLocale(); @Input() marketData: MarketData[]; @Input() symbol: string; + @Input() user: User; @Output() marketDataChanged = new EventEmitter(); public days = Array(31); public defaultDateFormat: string; public deviceType: string; + public historicalDataForm = this.formBuilder.group({ + historicalData: this.formBuilder.group({ + csvString: '' + }) + }); public historicalDataItems: LineChartItem[]; public marketDataByMonth: { [yearMonth: string]: { [day: string]: Pick & { day: number }; }; } = {}; - public user: User; + + private static readonly HISTORICAL_DATA_TEMPLATE = `date;marketPrice\n${format( + new Date(), + DATE_FORMAT + )};123.45`; private unsubscribeSubject = new Subject(); public constructor( + private adminService: AdminService, private deviceService: DeviceDetectorService, private dialog: MatDialog, - private userService: UserService + private formBuilder: FormBuilder, + private snackBar: MatSnackBar ) { this.deviceType = this.deviceService.getDeviceInfo().deviceType; + } - this.userService.stateChanged - .pipe(takeUntil(this.unsubscribeSubject)) - .subscribe((state) => { - if (state?.user) { - this.user = state.user; - } - }); + public ngOnInit() { + this.initializeHistoricalDataForm(); } public ngOnChanges() { @@ -177,29 +200,84 @@ export class AdminMarketDataDetailComponent implements OnChanges { }) { const marketPrice = this.marketDataByMonth[yearMonth]?.[day]?.marketPrice; - const dialogRef = this.dialog.open(MarketDataDetailDialog, { - data: { - marketPrice, - currency: this.currency, - dataSource: this.dataSource, - dateString: `${yearMonth}-${day}`, - symbol: this.symbol, - user: this.user - } as MarketDataDetailDialogParams, - height: this.deviceType === 'mobile' ? '98vh' : '80vh', - width: this.deviceType === 'mobile' ? '100vw' : '50rem' - }); + const dialogRef = this.dialog.open( + GfHistoricalMarketDataEditorDialogComponent, + { + data: { + marketPrice, + currency: this.currency, + dataSource: this.dataSource, + dateString: `${yearMonth}-${day}`, + symbol: this.symbol, + user: this.user + } as HistoricalMarketDataEditorDialogParams, + height: this.deviceType === 'mobile' ? '98vh' : '80vh', + width: this.deviceType === 'mobile' ? '100vw' : '50rem' + } + ); dialogRef .afterClosed() .pipe(takeUntil(this.unsubscribeSubject)) .subscribe(({ withRefresh } = { withRefresh: false }) => { - this.marketDataChanged.next(withRefresh); + this.marketDataChanged.emit(withRefresh); }); } + public onImportHistoricalData() { + try { + const marketData = csvToJson( + this.historicalDataForm.controls['historicalData'].controls['csvString'] + .value, + { + dynamicTyping: true, + header: true, + skipEmptyLines: true + } + ).data as UpdateMarketDataDto[]; + + this.adminService + .postMarketData({ + dataSource: this.dataSource, + marketData: { + marketData + }, + symbol: this.symbol + }) + .pipe( + catchError(({ error, message }) => { + this.snackBar.open(`${error}: ${message[0]}`, undefined, { + duration: ms('3 seconds') + }); + return EMPTY; + }), + takeUntil(this.unsubscribeSubject) + ) + .subscribe(() => { + this.initializeHistoricalDataForm(); + + this.marketDataChanged.emit(true); + }); + } catch { + this.snackBar.open( + $localize`Oops! Could not parse historical data.`, + undefined, + { duration: ms('3 seconds') } + ); + } + } + public ngOnDestroy() { this.unsubscribeSubject.next(); this.unsubscribeSubject.complete(); } + + private initializeHistoricalDataForm() { + this.historicalDataForm.setValue({ + historicalData: { + csvString: + GfHistoricalMarketDataEditorComponent.HISTORICAL_DATA_TEMPLATE + } + }); + } } diff --git a/libs/ui/src/lib/historical-market-data-editor/index.ts b/libs/ui/src/lib/historical-market-data-editor/index.ts new file mode 100644 index 00000000..6c7004ce --- /dev/null +++ b/libs/ui/src/lib/historical-market-data-editor/index.ts @@ -0,0 +1 @@ +export * from './historical-market-data-editor.component';