diff --git a/CHANGELOG.md b/CHANGELOG.md index 046d1b527..f069097ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Included cash in the performance calculation of the portfolio on the home page +## 3.27.0 - 2026-07-15 + +### Changed + +- Hardened the validation of the URL in the logo endpoint +- Set the change detection strategy to `OnPush` in the about pages +- Set the change detection strategy to `OnPush` in the accounts page +- Set the change detection strategy to `OnPush` in the demo page +- Set the change detection strategy to `OnPush` in the features page +- Set the change detection strategy to `OnPush` in the Frequently Asked Questions (FAQ) pages +- Set the change detection strategy to `OnPush` in the landing page +- Set the change detection strategy to `OnPush` in the markets page +- Set the change detection strategy to `OnPush` in the _Open Startup_ (`/open`) page +- Set the change detection strategy to `OnPush` in the pricing page +- Set the change detection strategy to `OnPush` in the public page +- Set the change detection strategy to `OnPush` in the registration page +- Set the change detection strategy to `OnPush` in the resources pages + +### Fixed + +- Fixed an issue where the symbol was not selected when cloning an activity +- Resolved a startup error in data gathering caused by uninitialized data provider mappings +- Improved the error handling in the `HtmlTemplateMiddleware` +- Improved the error handling in the get quotes functionality of the _Financial Modeling Prep_ service + ## 3.26.0 - 2026-07-14 ### Added diff --git a/apps/api/src/app/logo/get-logo.dto.ts b/apps/api/src/app/logo/get-logo.dto.ts new file mode 100644 index 000000000..e19753157 --- /dev/null +++ b/apps/api/src/app/logo/get-logo.dto.ts @@ -0,0 +1,9 @@ +import { IsUrl } from 'class-validator'; + +export class GetLogoDto { + @IsUrl({ + protocols: ['http', 'https'], + require_protocol: true + }) + url: string; +} diff --git a/apps/api/src/app/logo/logo.controller.ts b/apps/api/src/app/logo/logo.controller.ts index fdbe430c9..47bd1fc75 100644 --- a/apps/api/src/app/logo/logo.controller.ts +++ b/apps/api/src/app/logo/logo.controller.ts @@ -12,6 +12,7 @@ import { import { DataSource } from '@prisma/client'; import { Response } from 'express'; +import { GetLogoDto } from './get-logo.dto'; import { LogoService } from './logo.service'; @Controller('logo') @@ -41,7 +42,7 @@ export class LogoController { @Get() public async getLogoByUrl( - @Query('url') url: string, + @Query() { url }: GetLogoDto, @Res() response: Response ) { try { diff --git a/apps/api/src/app/logo/logo.service.ts b/apps/api/src/app/logo/logo.service.ts index 551d62438..e01e6cace 100644 --- a/apps/api/src/app/logo/logo.service.ts +++ b/apps/api/src/app/logo/logo.service.ts @@ -47,7 +47,7 @@ export class LogoService { private async getBuffer(aUrl: string) { const blob = await this.fetchService .fetch( - `https://t0.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${aUrl}&size=64`, + `https://t0.gstatic.com/faviconV2?client=SOCIAL&fallback_opts=TYPE,SIZE,URL&size=64&type=FAVICON&url=${encodeURIComponent(aUrl)}`, { headers: { 'User-Agent': 'request' }, signal: AbortSignal.timeout( diff --git a/apps/api/src/middlewares/html-template.middleware.ts b/apps/api/src/middlewares/html-template.middleware.ts index 325f64eaa..bdace3402 100644 --- a/apps/api/src/middlewares/html-template.middleware.ts +++ b/apps/api/src/middlewares/html-template.middleware.ts @@ -97,20 +97,29 @@ export class HtmlTemplateMiddleware implements NestMiddleware { private indexHtmlMap: { [languageCode: string]: string } = {}; public constructor(private readonly i18nService: I18nService) { - try { - this.indexHtmlMap = SUPPORTED_LANGUAGE_CODES.reduce( - (map, languageCode) => ({ - ...map, - [languageCode]: readFileSync( - join(__dirname, '..', 'client', languageCode, 'index.html'), - 'utf8' - ) - }), - {} - ); - } catch (error) { - this.logger.error('Failed to initialize index HTML map', error); + if (!environment.production) { + return; } + + this.indexHtmlMap = SUPPORTED_LANGUAGE_CODES.reduce((map, languageCode) => { + const indexHtmlPath = join( + __dirname, + '..', + 'client', + languageCode, + 'index.html' + ); + + try { + map[languageCode] = readFileSync(indexHtmlPath, 'utf8'); + } catch { + this.logger.warn( + `Skipping language '${languageCode}': ${indexHtmlPath} not found` + ); + } + + return map; + }, {}); } public use(request: Request, response: Response, next: NextFunction) { @@ -118,7 +127,8 @@ export class HtmlTemplateMiddleware implements NestMiddleware { let languageCode = path.substr(1, 2); if ( - !(SUPPORTED_LANGUAGE_CODES as readonly string[]).includes(languageCode) + !(SUPPORTED_LANGUAGE_CODES as readonly string[]).includes(languageCode) || + !this.indexHtmlMap[languageCode] ) { languageCode = DEFAULT_LANGUAGE_CODE; } 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 8723466b0..09bf1108e 100644 --- a/apps/api/src/services/data-provider/data-provider.service.ts +++ b/apps/api/src/services/data-provider/data-provider.service.ts @@ -44,7 +44,7 @@ import { AssetProfileInvalidError } from './errors/asset-profile-invalid.error'; export class DataProviderService implements OnModuleInit { private readonly logger = new Logger(DataProviderService.name); - private dataProviderMapping: { [dataProviderName: string]: string }; + private dataProviderMapping: { [dataProviderName: string]: string } = {}; public constructor( private readonly configurationService: ConfigurationService, 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 ec4aa39c0..4e3502033 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 @@ -47,7 +47,7 @@ import { isSameDay, parseISO } from 'date-fns'; -import { uniqBy } from 'lodash'; +import { isArray, uniqBy } from 'lodash'; @Injectable() export class FinancialModelingPrepService @@ -440,10 +440,14 @@ export class FinancialModelingPrepService signal: AbortSignal.timeout(requestTimeout) } ) - .then( - (res) => - res.json() as unknown as { price: number; symbol: string }[] - ) + .then(async (res) => { + const json = (await res.json()) as unknown as { + price: number; + symbol: string; + }[]; + + return isArray(json) ? json : []; + }) ]); for (const { currency, symbolTarget } of assetProfileResolutions) { 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 643737c4d..c2ed2f8f5 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 @@ -21,8 +21,8 @@ Get Access 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 d030abb82..c08f2d473 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 @@ -2,7 +2,10 @@ import { GfAdminPlatformComponent } from '@ghostfolio/client/components/admin-pl import { GfAdminTagComponent } from '@ghostfolio/client/components/admin-tag/admin-tag.component'; import { GfDataProviderStatusComponent } from '@ghostfolio/client/components/data-provider-status/data-provider-status.component'; import { UserService } from '@ghostfolio/client/services/user/user.service'; -import { PROPERTY_API_KEY_GHOSTFOLIO } from '@ghostfolio/common/config'; +import { + E_MAIL_LINE_BREAK, + PROPERTY_API_KEY_GHOSTFOLIO +} from '@ghostfolio/common/config'; import { ConfirmationDialogType } from '@ghostfolio/common/enums'; import { getDateFormatString } from '@ghostfolio/common/helper'; import { @@ -80,6 +83,13 @@ export class GfAdminSettingsComponent implements OnInit { public ghostfolioApiStatus: DataProviderGhostfolioStatusResponse; public isGhostfolioApiKeyValid: boolean; public isLoading = false; + public readonly premiumDataProviderMailHref = `mailto:hi@ghostfol.io?subject=Ghostfolio Premium Data Provider&body=${[ + 'Hello,', + '', + 'I am interested in the Ghostfolio Premium data provider. Could you please give me access so I can try it for some time?', + '', + 'Kind regards' + ].join(E_MAIL_LINE_BREAK)}`; public pricingUrl: string; public user: User; diff --git a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts index c5beb6c2d..52e0a14d4 100644 --- a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts +++ b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts @@ -1,6 +1,7 @@ import { UserService } from '@ghostfolio/client/services/user/user.service'; import { DEFAULT_PAGE_SIZE, + E_MAIL_LINE_BREAK, NUMERICAL_PRECISION_THRESHOLD_3_FIGURES, NUMERICAL_PRECISION_THRESHOLD_4_FIGURES } from '@ghostfolio/common/config'; @@ -169,7 +170,7 @@ export class GfHoldingDetailDialogComponent implements OnInit { protected readonly pageSize = DEFAULT_PAGE_SIZE; protected quantity: number; protected quantityPrecision = 2; - protected reportDataGlitchMail: string; + protected reportDataGlitchMailHref: string; protected readonly round = round; protected readonly routerLinkAdminControlMarketData = internalRoutes.adminControl.subRoutes.marketData.routerLink; @@ -456,7 +457,20 @@ export class GfHoldingDetailDialogComponent implements OnInit { this.assetProfile?.symbol ? ` (${this.assetProfile.symbol})` : '' }`; - this.reportDataGlitchMail = `mailto:hi@ghostfol.io?subject=${reportDataGlitchSubject}&body=Hello%0D%0DI would like to report a data glitch for%0D%0DSymbol: ${this.assetProfile?.symbol}%0DData Source: ${this.assetProfile?.dataSource}%0D%0DAdditional notes:%0D%0DCan you please take a look?%0D%0DKind regards`; + this.reportDataGlitchMailHref = `mailto:hi@ghostfol.io?subject=${reportDataGlitchSubject}&body=${[ + 'Hello', + '', + 'I would like to report a data glitch for', + '', + `Symbol: ${this.assetProfile?.symbol}`, + `Data Source: ${this.assetProfile?.dataSource}`, + '', + 'Additional notes:', + '', + 'Can you please take a look?', + '', + 'Kind regards' + ].join(E_MAIL_LINE_BREAK)}`; if (this.assetProfile?.assetClass) { this.assetClass = translate(this.assetProfile?.assetClass); diff --git a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html index 04388bddb..f233df8b6 100644 --- a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html +++ b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html @@ -456,7 +456,10 @@ dataSource?.data.length > 0 && data.hasPermissionToReportDataGlitch === true ) { - Report Data Glitch... @if (!user?.subscription?.expiresAt) { - Try Premium } @else if (hasPermissionToRequestOwnUserDeletion) { - Close Account } diff --git a/apps/client/src/app/pages/about/changelog/changelog-page.component.ts b/apps/client/src/app/pages/about/changelog/changelog-page.component.ts index d7f583bd1..24265f2ef 100644 --- a/apps/client/src/app/pages/about/changelog/changelog-page.component.ts +++ b/apps/client/src/app/pages/about/changelog/changelog-page.component.ts @@ -1,8 +1,9 @@ -import { Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; import { MarkdownModule } from 'ngx-markdown'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, imports: [MarkdownModule, NgxSkeletonLoaderModule], selector: 'gf-changelog-page', styleUrls: ['./changelog-page.scss'], diff --git a/apps/client/src/app/pages/about/license/license-page.component.ts b/apps/client/src/app/pages/about/license/license-page.component.ts index d530d0418..80f4eb0c0 100644 --- a/apps/client/src/app/pages/about/license/license-page.component.ts +++ b/apps/client/src/app/pages/about/license/license-page.component.ts @@ -1,7 +1,8 @@ -import { Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; import { MarkdownModule } from 'ngx-markdown'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, imports: [MarkdownModule], selector: 'gf-license-page', styleUrls: ['./license-page.scss'], diff --git a/apps/client/src/app/pages/about/oss-friends/oss-friends-page.component.ts b/apps/client/src/app/pages/about/oss-friends/oss-friends-page.component.ts index c2e500a52..cb2aa6b07 100644 --- a/apps/client/src/app/pages/about/oss-friends/oss-friends-page.component.ts +++ b/apps/client/src/app/pages/about/oss-friends/oss-friends-page.component.ts @@ -1,4 +1,4 @@ -import { Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { IonIcon } from '@ionic/angular/standalone'; @@ -8,6 +8,7 @@ import { arrowForwardOutline } from 'ionicons/icons'; const ossFriends = require('../../../../assets/oss-friends.json'); @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, imports: [IonIcon, MatButtonModule, MatCardModule], selector: 'gf-oss-friends-page', styleUrls: ['./oss-friends-page.scss'], diff --git a/apps/client/src/app/pages/about/overview/about-overview-page.component.ts b/apps/client/src/app/pages/about/overview/about-overview-page.component.ts index 92a98598b..5af3c48ec 100644 --- a/apps/client/src/app/pages/about/overview/about-overview-page.component.ts +++ b/apps/client/src/app/pages/about/overview/about-overview-page.component.ts @@ -5,6 +5,7 @@ import { publicRoutes } from '@ghostfolio/common/routes/routes'; import { DataService } from '@ghostfolio/ui/services'; import { + ChangeDetectionStrategy, ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA, @@ -25,6 +26,7 @@ import { } from 'ionicons/icons'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, imports: [IonIcon, MatButtonModule, RouterModule], schemas: [CUSTOM_ELEMENTS_SCHEMA], selector: 'gf-about-overview-page', @@ -68,9 +70,9 @@ export class GfAboutOverviewPageComponent implements OnInit { .subscribe((state) => { if (state?.user) { this.user = state.user; - - this.changeDetectorRef.markForCheck(); } + + this.changeDetectorRef.markForCheck(); }); } } diff --git a/apps/client/src/app/pages/about/privacy-policy/privacy-policy-page.component.ts b/apps/client/src/app/pages/about/privacy-policy/privacy-policy-page.component.ts index 50e4e3e2f..cf205533a 100644 --- a/apps/client/src/app/pages/about/privacy-policy/privacy-policy-page.component.ts +++ b/apps/client/src/app/pages/about/privacy-policy/privacy-policy-page.component.ts @@ -1,7 +1,8 @@ -import { Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; import { MarkdownModule } from 'ngx-markdown'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, imports: [MarkdownModule], selector: 'gf-privacy-policy-page', styleUrls: ['./privacy-policy-page.scss'], diff --git a/apps/client/src/app/pages/about/terms-of-service/terms-of-service-page.component.ts b/apps/client/src/app/pages/about/terms-of-service/terms-of-service-page.component.ts index 7899f0187..77f29c07e 100644 --- a/apps/client/src/app/pages/about/terms-of-service/terms-of-service-page.component.ts +++ b/apps/client/src/app/pages/about/terms-of-service/terms-of-service-page.component.ts @@ -1,7 +1,8 @@ -import { Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; import { MarkdownModule } from 'ngx-markdown'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, imports: [MarkdownModule], selector: 'gf-terms-of-service-page', styleUrls: ['./terms-of-service-page.scss'], diff --git a/apps/client/src/app/pages/accounts/accounts-page.component.ts b/apps/client/src/app/pages/accounts/accounts-page.component.ts index 907b39d37..7d5e2fff7 100644 --- a/apps/client/src/app/pages/accounts/accounts-page.component.ts +++ b/apps/client/src/app/pages/accounts/accounts-page.component.ts @@ -15,6 +15,7 @@ import { NotificationService } from '@ghostfolio/ui/notifications'; import { DataService } from '@ghostfolio/ui/services'; import { + ChangeDetectionStrategy, ChangeDetectorRef, Component, computed, @@ -36,6 +37,7 @@ import { TransferBalanceDialogParams } from './transfer-balance/interfaces/inter import { GfTransferBalanceDialogComponent } from './transfer-balance/transfer-balance-dialog.component'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'page' }, imports: [GfAccountsTableComponent, GfFabComponent, RouterModule], selector: 'gf-accounts-page', @@ -120,9 +122,9 @@ export class GfAccountsPageComponent implements OnInit { this.user.permissions, permissions.updateAccount ); - - this.changeDetectorRef.markForCheck(); } + + this.changeDetectorRef.markForCheck(); }); this.fetchAccounts(); 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 3373977cc..556890271 100644 --- a/apps/client/src/app/pages/api/api-page.component.ts +++ b/apps/client/src/app/pages/api/api-page.component.ts @@ -22,7 +22,13 @@ import { HttpHeaders, HttpParams } from '@angular/common/http'; -import { Component, DestroyRef, inject, OnInit } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + DestroyRef, + inject, + OnInit +} from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatCardModule } from '@angular/material/card'; import { format, startOfYear } from 'date-fns'; @@ -33,6 +39,7 @@ import { catchError, map, Observable, of, OperatorFunction } from 'rxjs'; import { FetchFailure, FetchResult } from './interfaces/interfaces'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'page' }, imports: [ CommonModule, diff --git a/apps/client/src/app/pages/auth/auth-page.component.ts b/apps/client/src/app/pages/auth/auth-page.component.ts index 1b0b4a67c..7c501403e 100644 --- a/apps/client/src/app/pages/auth/auth-page.component.ts +++ b/apps/client/src/app/pages/auth/auth-page.component.ts @@ -4,11 +4,17 @@ import { } from '@ghostfolio/client/services/settings-storage.service'; import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service'; -import { Component, DestroyRef, OnInit } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + DestroyRef, + OnInit +} from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { ActivatedRoute, Router } from '@angular/router'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, selector: 'gf-auth-page', styleUrls: ['./auth-page.scss'], templateUrl: './auth-page.html' diff --git a/apps/client/src/app/pages/demo/demo-page.component.ts b/apps/client/src/app/pages/demo/demo-page.component.ts index 1f49039ce..a4482e36c 100644 --- a/apps/client/src/app/pages/demo/demo-page.component.ts +++ b/apps/client/src/app/pages/demo/demo-page.component.ts @@ -3,10 +3,11 @@ import { InfoItem } from '@ghostfolio/common/interfaces'; import { NotificationService } from '@ghostfolio/ui/notifications'; import { DataService } from '@ghostfolio/ui/services'; -import { Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; import { Router } from '@angular/router'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'page' }, selector: 'gf-demo-page', standalone: true, diff --git a/apps/client/src/app/pages/faq/overview/faq-overview-page.component.ts b/apps/client/src/app/pages/faq/overview/faq-overview-page.component.ts index 1d59e67e8..17d93e795 100644 --- a/apps/client/src/app/pages/faq/overview/faq-overview-page.component.ts +++ b/apps/client/src/app/pages/faq/overview/faq-overview-page.component.ts @@ -4,6 +4,7 @@ import { publicRoutes } from '@ghostfolio/common/routes/routes'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; import { + ChangeDetectionStrategy, ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA, @@ -14,6 +15,7 @@ import { MatCardModule } from '@angular/material/card'; import { RouterModule } from '@angular/router'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'page' }, imports: [GfPremiumIndicatorComponent, MatCardModule, RouterModule], schemas: [CUSTOM_ELEMENTS_SCHEMA], @@ -38,9 +40,9 @@ export class GfFaqOverviewPageComponent { .subscribe((state) => { if (state?.user) { this.user = state.user; - - this.changeDetectorRef.markForCheck(); } + + this.changeDetectorRef.markForCheck(); }); } } diff --git a/apps/client/src/app/pages/faq/saas/saas-page.component.ts b/apps/client/src/app/pages/faq/saas/saas-page.component.ts index 3f44653d2..282ade32c 100644 --- a/apps/client/src/app/pages/faq/saas/saas-page.component.ts +++ b/apps/client/src/app/pages/faq/saas/saas-page.component.ts @@ -4,6 +4,7 @@ import { internalRoutes, publicRoutes } from '@ghostfolio/common/routes/routes'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; import { + ChangeDetectionStrategy, ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA, @@ -14,6 +15,7 @@ import { MatCardModule } from '@angular/material/card'; import { RouterModule } from '@angular/router'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'page' }, imports: [GfPremiumIndicatorComponent, MatCardModule, RouterModule], schemas: [CUSTOM_ELEMENTS_SCHEMA], @@ -42,9 +44,9 @@ export class GfSaasPageComponent { .subscribe((state) => { if (state?.user) { this.user = state.user; - - this.changeDetectorRef.markForCheck(); } + + this.changeDetectorRef.markForCheck(); }); } } diff --git a/apps/client/src/app/pages/faq/self-hosting/self-hosting-page.component.ts b/apps/client/src/app/pages/faq/self-hosting/self-hosting-page.component.ts index f2468c7d4..dcfe9e2a4 100644 --- a/apps/client/src/app/pages/faq/self-hosting/self-hosting-page.component.ts +++ b/apps/client/src/app/pages/faq/self-hosting/self-hosting-page.component.ts @@ -1,11 +1,16 @@ import { publicRoutes } from '@ghostfolio/common/routes/routes'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; -import { CUSTOM_ELEMENTS_SCHEMA, Component } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + CUSTOM_ELEMENTS_SCHEMA +} from '@angular/core'; import { MatCardModule } from '@angular/material/card'; import { RouterModule } from '@angular/router'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'page' }, imports: [GfPremiumIndicatorComponent, MatCardModule, RouterModule], schemas: [CUSTOM_ELEMENTS_SCHEMA], diff --git a/apps/client/src/app/pages/features/features-page.component.ts b/apps/client/src/app/pages/features/features-page.component.ts index cef90f2e5..dabfa47ef 100644 --- a/apps/client/src/app/pages/features/features-page.component.ts +++ b/apps/client/src/app/pages/features/features-page.component.ts @@ -5,13 +5,19 @@ import { publicRoutes } from '@ghostfolio/common/routes/routes'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; import { DataService } from '@ghostfolio/ui/services'; -import { ChangeDetectorRef, Component, DestroyRef } from '@angular/core'; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + DestroyRef +} from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { RouterModule } from '@angular/router'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'page' }, imports: [ GfPremiumIndicatorComponent, @@ -46,9 +52,9 @@ export class GfFeaturesPageComponent { .subscribe((state) => { if (state?.user) { this.user = state.user; - - this.changeDetectorRef.markForCheck(); } + + this.changeDetectorRef.markForCheck(); }); this.hasPermissionForSubscription = hasPermission( diff --git a/apps/client/src/app/pages/i18n/i18n-page.component.ts b/apps/client/src/app/pages/i18n/i18n-page.component.ts index 76d123914..c1f394b00 100644 --- a/apps/client/src/app/pages/i18n/i18n-page.component.ts +++ b/apps/client/src/app/pages/i18n/i18n-page.component.ts @@ -1,6 +1,7 @@ -import { Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'page' }, selector: 'gf-i18n-page', standalone: true, diff --git a/apps/client/src/app/pages/landing/landing-page.component.ts b/apps/client/src/app/pages/landing/landing-page.component.ts index 386992a6e..8b8d4dcfd 100644 --- a/apps/client/src/app/pages/landing/landing-page.component.ts +++ b/apps/client/src/app/pages/landing/landing-page.component.ts @@ -8,7 +8,7 @@ import { DataService } from '@ghostfolio/ui/services'; import { GfValueComponent } from '@ghostfolio/ui/value'; import { GfWorldMapChartComponent } from '@ghostfolio/ui/world-map-chart'; -import { Component, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { RouterModule } from '@angular/router'; @@ -22,6 +22,7 @@ import { import { DeviceDetectorService } from 'ngx-device-detector'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'page' }, imports: [ GfCarouselComponent, diff --git a/apps/client/src/app/pages/markets/markets-page.component.ts b/apps/client/src/app/pages/markets/markets-page.component.ts index c70cb8120..9a7576a95 100644 --- a/apps/client/src/app/pages/markets/markets-page.component.ts +++ b/apps/client/src/app/pages/markets/markets-page.component.ts @@ -1,8 +1,9 @@ import { GfHomeMarketComponent } from '@ghostfolio/client/components/home-market/home-market.component'; -import { Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'page' }, imports: [GfHomeMarketComponent], selector: 'gf-markets-page', diff --git a/apps/client/src/app/pages/open/open-page.component.ts b/apps/client/src/app/pages/open/open-page.component.ts index 090588d7d..93346ecaa 100644 --- a/apps/client/src/app/pages/open/open-page.component.ts +++ b/apps/client/src/app/pages/open/open-page.component.ts @@ -4,6 +4,7 @@ import { DataService } from '@ghostfolio/ui/services'; import { GfValueComponent } from '@ghostfolio/ui/value'; import { + ChangeDetectionStrategy, ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA, @@ -14,6 +15,7 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MatCardModule } from '@angular/material/card'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'page' }, imports: [GfValueComponent, MatCardModule], schemas: [CUSTOM_ELEMENTS_SCHEMA], @@ -42,9 +44,9 @@ export class GfOpenPageComponent implements OnInit { .subscribe((state) => { if (state?.user) { this.user = state.user; - - this.changeDetectorRef.markForCheck(); } + + this.changeDetectorRef.markForCheck(); }); } } diff --git a/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts b/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts index 3d1c0ceeb..45653ff80 100644 --- a/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts +++ b/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts @@ -409,7 +409,7 @@ export class GfActivitiesPageComponent implements OnInit { activity: { ...aActivity, accountId: aActivity?.accountId, - assetProfile: null, + assetProfile: aActivity?.assetProfile ?? null, date: new Date(), id: null, fee: 0, 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 c0e3848bb..8b4e07ffb 100644 --- a/apps/client/src/app/pages/pricing/pricing-page.component.ts +++ b/apps/client/src/app/pages/pricing/pricing-page.component.ts @@ -8,6 +8,7 @@ import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; import { DataService } from '@ghostfolio/ui/services'; import { + ChangeDetectionStrategy, ChangeDetectorRef, Component, CUSTOM_ELEMENTS_SCHEMA, @@ -32,6 +33,7 @@ import { EMPTY } from 'rxjs'; import { catchError } from 'rxjs/operators'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'page' }, imports: [ GfPremiumIndicatorComponent, @@ -123,9 +125,9 @@ export class GfPricingPageComponent implements OnInit { this.label = this.user?.subscription?.offer?.label; this.price = this.user?.subscription?.offer?.price; this.priceId = this.user?.subscription?.offer?.priceId; - - this.changeDetectorRef.markForCheck(); } + + this.changeDetectorRef.markForCheck(); }); } diff --git a/apps/client/src/app/pages/public/public-page.component.ts b/apps/client/src/app/pages/public/public-page.component.ts index 91410d5f7..52d295dfa 100644 --- a/apps/client/src/app/pages/public/public-page.component.ts +++ b/apps/client/src/app/pages/public/public-page.component.ts @@ -17,6 +17,7 @@ import { GfWorldMapChartComponent } from '@ghostfolio/ui/world-map-chart'; import { HttpErrorResponse } from '@angular/common/http'; import { + ChangeDetectionStrategy, ChangeDetectorRef, Component, computed, @@ -38,6 +39,7 @@ import { EMPTY } from 'rxjs'; import { catchError } from 'rxjs/operators'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'page' }, imports: [ GfActivitiesTableComponent, diff --git a/apps/client/src/app/pages/register/register-page.component.ts b/apps/client/src/app/pages/register/register-page.component.ts index ecc83d8f3..6a4825c4d 100644 --- a/apps/client/src/app/pages/register/register-page.component.ts +++ b/apps/client/src/app/pages/register/register-page.component.ts @@ -6,6 +6,7 @@ import { GfLogoComponent } from '@ghostfolio/ui/logo'; import { DataService } from '@ghostfolio/ui/services'; import { + ChangeDetectionStrategy, Component, CUSTOM_ELEMENTS_SCHEMA, DestroyRef, @@ -21,6 +22,7 @@ import { UserAccountRegistrationDialogParams } from './user-account-registration import { GfUserAccountRegistrationDialogComponent } from './user-account-registration-dialog/user-account-registration-dialog.component'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'page' }, imports: [GfLogoComponent, MatButtonModule, RouterModule], schemas: [CUSTOM_ELEMENTS_SCHEMA], diff --git a/apps/client/src/app/pages/resources/glossary/resources-glossary.component.ts b/apps/client/src/app/pages/resources/glossary/resources-glossary.component.ts index 112619239..d0aa5e923 100644 --- a/apps/client/src/app/pages/resources/glossary/resources-glossary.component.ts +++ b/apps/client/src/app/pages/resources/glossary/resources-glossary.component.ts @@ -3,10 +3,11 @@ import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { publicRoutes } from '@ghostfolio/common/routes/routes'; import { DataService } from '@ghostfolio/ui/services'; -import { Component, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { RouterModule } from '@angular/router'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, imports: [RouterModule], selector: 'gf-resources-glossary', styleUrls: ['./resources-glossary.component.scss'], diff --git a/apps/client/src/app/pages/resources/guides/resources-guides.component.ts b/apps/client/src/app/pages/resources/guides/resources-guides.component.ts index 52c317cea..9f2556195 100644 --- a/apps/client/src/app/pages/resources/guides/resources-guides.component.ts +++ b/apps/client/src/app/pages/resources/guides/resources-guides.component.ts @@ -1,7 +1,8 @@ -import { Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; import { RouterModule } from '@angular/router'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, imports: [RouterModule], selector: 'gf-resources-guides', styleUrls: ['./resources-guides.component.scss'], diff --git a/apps/client/src/app/pages/resources/markets/resources-markets.component.ts b/apps/client/src/app/pages/resources/markets/resources-markets.component.ts index 79c185959..71660504a 100644 --- a/apps/client/src/app/pages/resources/markets/resources-markets.component.ts +++ b/apps/client/src/app/pages/resources/markets/resources-markets.component.ts @@ -1,6 +1,7 @@ -import { Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, selector: 'gf-resources-markets', styleUrls: ['./resources-markets.component.scss'], templateUrl: './resources-markets.component.html' diff --git a/apps/client/src/app/pages/resources/overview/resources-overview.component.ts b/apps/client/src/app/pages/resources/overview/resources-overview.component.ts index 81338200f..7830b2eec 100644 --- a/apps/client/src/app/pages/resources/overview/resources-overview.component.ts +++ b/apps/client/src/app/pages/resources/overview/resources-overview.component.ts @@ -1,9 +1,10 @@ import { publicRoutes } from '@ghostfolio/common/routes/routes'; -import { Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; import { RouterModule } from '@angular/router'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, imports: [RouterModule], selector: 'gf-resources-overview', styleUrls: ['./resources-overview.component.scss'], diff --git a/apps/client/src/app/pages/resources/personal-finance-tools/personal-finance-tools-page.component.ts b/apps/client/src/app/pages/resources/personal-finance-tools/personal-finance-tools-page.component.ts index bb4ae3889..27a4f3265 100644 --- a/apps/client/src/app/pages/resources/personal-finance-tools/personal-finance-tools-page.component.ts +++ b/apps/client/src/app/pages/resources/personal-finance-tools/personal-finance-tools-page.component.ts @@ -1,7 +1,7 @@ import { personalFinanceTools } from '@ghostfolio/common/personal-finance-tools'; import { publicRoutes } from '@ghostfolio/common/routes/routes'; -import { Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component } from '@angular/core'; import { MatCardModule } from '@angular/material/card'; import { RouterModule } from '@angular/router'; import { IonIcon } from '@ionic/angular/standalone'; @@ -9,6 +9,7 @@ import { addIcons } from 'ionicons'; import { chevronForwardOutline } from 'ionicons/icons'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'page' }, imports: [IonIcon, MatCardModule, RouterModule], selector: 'gf-personal-finance-tools-page', diff --git a/apps/client/src/app/pages/webauthn/webauthn-page.component.ts b/apps/client/src/app/pages/webauthn/webauthn-page.component.ts index d988e1bb9..05a450239 100644 --- a/apps/client/src/app/pages/webauthn/webauthn-page.component.ts +++ b/apps/client/src/app/pages/webauthn/webauthn-page.component.ts @@ -2,6 +2,7 @@ import { TokenStorageService } from '@ghostfolio/client/services/token-storage.s import { WebAuthnService } from '@ghostfolio/client/services/web-authn.service'; import { + ChangeDetectionStrategy, ChangeDetectorRef, Component, DestroyRef, @@ -13,6 +14,7 @@ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { Router } from '@angular/router'; @Component({ + changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'page' }, imports: [MatButtonModule, MatProgressSpinnerModule], selector: 'gf-webauthn-page', @@ -56,7 +58,9 @@ export class GfWebauthnPageComponent implements OnInit { }, (error) => { console.error(error); + this.hasError = true; + this.changeDetectorRef.markForCheck(); } ); diff --git a/libs/common/src/lib/config.ts b/libs/common/src/lib/config.ts index e071a72c5..f7bafb101 100644 --- a/libs/common/src/lib/config.ts +++ b/libs/common/src/lib/config.ts @@ -184,6 +184,8 @@ export const DERIVED_CURRENCIES = [ } ]; +export const E_MAIL_LINE_BREAK = '%0D%0A'; + export const GATHER_ASSET_PROFILE_PROCESS_JOB_NAME = 'GATHER_ASSET_PROFILE'; export const GATHER_ASSET_PROFILE_PROCESS_JOB_OPTIONS: JobOptions = { attempts: 12, diff --git a/libs/ui/src/lib/entity-logo/entity-logo-image-source.service.ts b/libs/ui/src/lib/entity-logo/entity-logo-image-source.service.ts index 9cbea529b..db916a34f 100644 --- a/libs/ui/src/lib/entity-logo/entity-logo-image-source.service.ts +++ b/libs/ui/src/lib/entity-logo/entity-logo-image-source.service.ts @@ -15,6 +15,6 @@ export class EntityLogoImageSourceService { } public getLogoUrlByUrl(url: string) { - return `../api/v1/logo?url=${url}`; + return `../api/v1/logo?url=${encodeURIComponent(url)}`; } } diff --git a/package-lock.json b/package-lock.json index 00128128e..efc1f9d7c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ghostfolio", - "version": "3.26.0", + "version": "3.27.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ghostfolio", - "version": "3.26.0", + "version": "3.27.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/package.json b/package.json index 384ca8a8b..39df1afa5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ghostfolio", - "version": "3.26.0", + "version": "3.27.0", "homepage": "https://ghostfol.io", "license": "AGPL-3.0", "repository": "https://github.com/ghostfolio/ghostfolio",