From ec92116acc0ec327c24622dd79b3ef58197accf0 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 22 Feb 2026 08:59:52 +0100 Subject: [PATCH 01/20] Task/remove unused OnDestroy hook in admin page component (#6366) * Remove unused OnDestroy hook --- .../src/app/pages/admin/admin-page.component.ts | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/apps/client/src/app/pages/admin/admin-page.component.ts b/apps/client/src/app/pages/admin/admin-page.component.ts index b9243dcb9..284d3c41d 100644 --- a/apps/client/src/app/pages/admin/admin-page.component.ts +++ b/apps/client/src/app/pages/admin/admin-page.component.ts @@ -1,7 +1,7 @@ import { TabConfiguration } from '@ghostfolio/common/interfaces'; import { internalRoutes } from '@ghostfolio/common/routes/routes'; -import { Component, OnDestroy, OnInit } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { MatTabsModule } from '@angular/material/tabs'; import { RouterModule } from '@angular/router'; import { IonIcon } from '@ionic/angular/standalone'; @@ -14,7 +14,6 @@ import { settingsOutline } from 'ionicons/icons'; import { DeviceDetectorService } from 'ngx-device-detector'; -import { Subject } from 'rxjs'; @Component({ host: { class: 'page has-tabs' }, @@ -23,12 +22,10 @@ import { Subject } from 'rxjs'; styleUrls: ['./admin-page.scss'], templateUrl: './admin-page.html' }) -export class AdminPageComponent implements OnDestroy, OnInit { +export class AdminPageComponent implements OnInit { public deviceType: string; public tabs: TabConfiguration[] = []; - private unsubscribeSubject = new Subject(); - public constructor(private deviceService: DeviceDetectorService) { addIcons({ flashOutline, @@ -74,9 +71,4 @@ export class AdminPageComponent implements OnDestroy, OnInit { } ]; } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } From e3579f6811df4e83f7e566f57b7a8b94f971b7f2 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 22 Feb 2026 09:00:45 +0100 Subject: [PATCH 02/20] Task/add missing OnDestroy hook in user account registration dialog (#6368) * Add missing OnDestroy hook --- .../user-account-registration-dialog.component.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.component.ts b/apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.component.ts index a7707ad3b..4cc0f52f8 100644 --- a/apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.component.ts +++ b/apps/client/src/app/pages/register/user-account-registration-dialog/user-account-registration-dialog.component.ts @@ -9,6 +9,7 @@ import { Component, CUSTOM_ELEMENTS_SCHEMA, Inject, + OnDestroy, ViewChild } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; @@ -52,7 +53,7 @@ import { UserAccountRegistrationDialogParams } from './interfaces/interfaces'; styleUrls: ['./user-account-registration-dialog.scss'], templateUrl: 'user-account-registration-dialog.html' }) -export class GfUserAccountRegistrationDialogComponent { +export class GfUserAccountRegistrationDialogComponent implements OnDestroy { @ViewChild(MatStepper) stepper!: MatStepper; public accessToken: string; @@ -95,4 +96,9 @@ export class GfUserAccountRegistrationDialogComponent { public onChangeDislaimerChecked() { this.isDisclaimerChecked = !this.isDisclaimerChecked; } + + public ngOnDestroy() { + this.unsubscribeSubject.next(); + this.unsubscribeSubject.complete(); + } } From 5acbe25d2504a675043199a2c3cc5ab62a33e58d Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 22 Feb 2026 09:01:48 +0100 Subject: [PATCH 03/20] Task/clean up changelog (#6362) * Clean up --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f485abeb..2627f1a19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed an issue with `balanceInBaseCurrency` of the accounts in the value redaction interceptor for the impersonation mode - Fixed an issue with `comment` of the accounts in the value redaction interceptor for the impersonation mode - Fixed an issue with `dividendInBaseCurrency` of the accounts in the value redaction interceptor for the impersonation mode -- Fixed an issue with `dividendInBaseCurrency` of the accounts in the value redaction interceptor for the impersonation mode - Fixed an issue with `interestInBaseCurrency` of the accounts in the value redaction interceptor for the impersonation mode - Fixed an issue with `value` of the accounts in the value redaction interceptor for the impersonation mode From 97915a3ca9acaece4da9f844c566fad4fd4643ea Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 22 Feb 2026 09:02:37 +0100 Subject: [PATCH 04/20] Bugfix/fix page size for presets in market data table of admin control panel (#6363) * Fix page size for presets * Update changelog --- CHANGELOG.md | 6 ++++++ .../admin-market-data/admin-market-data.component.ts | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2627f1a19..f4a857ab3 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 page size for presets in the historical market data table of the admin control panel + ## 2.241.0 - 2026-02-21 ### Changed diff --git a/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts b/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts index 6a079c20a..0beca6f3c 100644 --- a/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts +++ b/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts @@ -379,7 +379,7 @@ export class GfAdminMarketDataComponent this.pageSize = this.activeFilters.length === 1 && this.activeFilters[0].type === 'PRESET_ID' - ? undefined + ? Number.MAX_SAFE_INTEGER : DEFAULT_PAGE_SIZE; if (pageIndex === 0 && this.paginator) { From 4897f34d51e0451b18a88cded85c5bfd41d009e0 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 22 Feb 2026 10:31:59 +0100 Subject: [PATCH 05/20] Task/change account field to optional in create or update activity dialog (#6371) * Change account field to optional for every case * Update changelog --- CHANGELOG.md | 4 ++++ .../create-or-update-activity-dialog.component.ts | 15 +-------------- .../create-or-update-activity-dialog.html | 8 ++------ 3 files changed, 7 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4a857ab3..ecd2ce5c8 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 + +- Changed the account field to optional in the create or update activity dialog + ### Fixed - Fixed the page size for presets in the historical market data table of the admin control panel diff --git a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts index 8695f04ed..fc42a504d 100644 --- a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts +++ b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts @@ -188,8 +188,7 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy { !this.data.activity?.accountId && this.mode === 'create' ? this.data.accounts[0].id - : this.data.activity?.accountId, - Validators.required + : this.data.activity?.accountId ], assetClass: [this.data.activity?.SymbolProfile?.assetClass], assetSubClass: [this.data.activity?.SymbolProfile?.assetSubClass], @@ -365,11 +364,6 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy { (this.activityForm.get('dataSource').value === 'MANUAL' && type === 'BUY') ) { - this.activityForm - .get('accountId') - .removeValidators(Validators.required); - this.activityForm.get('accountId').updateValueAndValidity(); - const currency = this.data.accounts.find(({ id }) => { return id === this.activityForm.get('accountId').value; @@ -397,11 +391,6 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy { this.activityForm.get('updateAccountBalance').disable(); this.activityForm.get('updateAccountBalance').setValue(false); } else if (['FEE', 'INTEREST', 'LIABILITY'].includes(type)) { - this.activityForm - .get('accountId') - .removeValidators(Validators.required); - this.activityForm.get('accountId').updateValueAndValidity(); - const currency = this.data.accounts.find(({ id }) => { return id === this.activityForm.get('accountId').value; @@ -447,8 +436,6 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy { this.activityForm.get('updateAccountBalance').setValue(false); } } else { - this.activityForm.get('accountId').setValidators(Validators.required); - this.activityForm.get('accountId').updateValueAndValidity(); this.activityForm .get('dataSource') .setValidators(Validators.required); diff --git a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html index 42fbd0ebf..278ddcbbd 100644 --- a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html +++ b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.html @@ -84,12 +84,8 @@ > Account - @if ( - !activityForm.get('accountId').hasValidator(Validators.required) || - (!activityForm.get('accountId').value && mode === 'update') - ) { - - } + + @for (account of data.accounts; track account) {
From 3eb9d53220dd158917281db85c62dc23876e36e9 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 22 Feb 2026 10:45:14 +0100 Subject: [PATCH 06/20] Bugfix/validation of valuables (#6372) * Fix validation of valuables * Update changelog --- CHANGELOG.md | 1 + .../services/data-provider/data-provider.service.ts | 12 ++++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ecd2ce5c8..34aff43e1 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 a validation issue for valuables used in the create and import activity logic - Fixed the page size for presets in the historical market data table of the admin control panel ## 2.241.0 - 2026-02-21 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 eb9816c67..8ee8761c0 100644 --- a/apps/api/src/services/data-provider/data-provider.service.ts +++ b/apps/api/src/services/data-provider/data-provider.service.ts @@ -244,11 +244,15 @@ export class DataProviderService implements OnModuleInit { }); if (!assetProfiles[assetProfileIdentifier]) { - if (['FEE', 'INTEREST', 'LIABILITY'].includes(type)) { + if ( + (dataSource === DataSource.MANUAL && type === 'BUY') || + ['FEE', 'INTEREST', 'LIABILITY'].includes(type) + ) { const assetProfileInImport = assetProfilesWithMarketDataDto?.find( - (profile) => { + (assetProfile) => { return ( - profile.dataSource === dataSource && profile.symbol === symbol + assetProfile.dataSource === dataSource && + assetProfile.symbol === symbol ); } ); @@ -257,7 +261,7 @@ export class DataProviderService implements OnModuleInit { currency, dataSource, symbol, - name: assetProfileInImport?.name + name: assetProfileInImport?.name ?? symbol }; continue; From 98891e195c569b762389723a5258c0c8c084a945 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 22 Feb 2026 11:01:18 +0100 Subject: [PATCH 07/20] Release 2.242.0 (#6375) * Release 2.242.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 34aff43e1..91f80d3ba 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.242.0 - 2026-02-22 ### Changed diff --git a/package-lock.json b/package-lock.json index 2a6d30cc8..7a1ebfa67 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ghostfolio", - "version": "2.241.0", + "version": "2.242.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ghostfolio", - "version": "2.241.0", + "version": "2.242.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/package.json b/package.json index 3dcfde537..2bf5eebe0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ghostfolio", - "version": "2.241.0", + "version": "2.242.0", "homepage": "https://ghostfol.io", "license": "AGPL-3.0", "repository": "https://github.com/ghostfolio/ghostfolio", From 14f0d2bd8b827c1cc9fe5851c604b02c897fb792 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 23 Feb 2026 17:27:04 +0100 Subject: [PATCH 08/20] Task/remove unused OnDestroy hook in OSS friends page component (#6374) * Remove unused OnDestroy hook --- .../about/oss-friends/oss-friends-page.component.ts | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) 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 bdbbdf9a7..c2e500a52 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,10 +1,9 @@ -import { Component, OnDestroy } from '@angular/core'; +import { Component } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { IonIcon } from '@ionic/angular/standalone'; import { addIcons } from 'ionicons'; import { arrowForwardOutline } from 'ionicons/icons'; -import { Subject } from 'rxjs'; const ossFriends = require('../../../../assets/oss-friends.json'); @@ -14,17 +13,10 @@ const ossFriends = require('../../../../assets/oss-friends.json'); styleUrls: ['./oss-friends-page.scss'], templateUrl: './oss-friends-page.html' }) -export class GfOpenSourceSoftwareFriendsPageComponent implements OnDestroy { +export class GfOpenSourceSoftwareFriendsPageComponent { public ossFriends = ossFriends.data; - private unsubscribeSubject = new Subject(); - public constructor() { addIcons({ arrowForwardOutline }); } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } From c7cfc87e208d584d43875c7c336a0e171f65d371 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 23 Feb 2026 17:29:32 +0100 Subject: [PATCH 09/20] Task/remove unused OnDestroy hook in changelog page component (#6373) * Remove unused OnDestroy hook --- .../about/changelog/changelog-page.component.ts | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) 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 69b397370..d7f583bd1 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,7 +1,6 @@ -import { Component, OnDestroy } from '@angular/core'; +import { Component } from '@angular/core'; import { MarkdownModule } from 'ngx-markdown'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; -import { Subject } from 'rxjs'; @Component({ imports: [MarkdownModule, NgxSkeletonLoaderModule], @@ -9,17 +8,10 @@ import { Subject } from 'rxjs'; styleUrls: ['./changelog-page.scss'], templateUrl: './changelog-page.html' }) -export class GfChangelogPageComponent implements OnDestroy { +export class GfChangelogPageComponent { public isLoading = true; - private unsubscribeSubject = new Subject(); - public onLoad() { this.isLoading = false; } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } From f5d99bad241440651f9fe473d3f2113b44635400 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 23 Feb 2026 20:05:04 +0100 Subject: [PATCH 10/20] Bugfix/create activities of type fee, interest or liability (#6378) * Fix creation of activities with type FEE, INTEREST or LIABILITY * Update changelog --- CHANGELOG.md | 6 ++++++ .../create-or-update-activity-dialog.component.ts | 9 +++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91f80d3ba..160b4cbf0 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 an issue when creating activities of type `FEE`, `INTEREST` or `LIABILITY` + ## 2.242.0 - 2026-02-22 ### Changed diff --git a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts index fc42a504d..c7cd63191 100644 --- a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts +++ b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts @@ -501,11 +501,12 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy { comment: this.activityForm.get('comment').value || null, currency: this.activityForm.get('currency').value, customCurrency: this.activityForm.get('currencyOfUnitPrice').value, + dataSource: ['FEE', 'INTEREST', 'LIABILITY', 'VALUABLE'].includes( + this.activityForm.get('type').value + ) + ? 'MANUAL' + : this.activityForm.get('dataSource').value, date: this.activityForm.get('date').value, - dataSource: - this.activityForm.get('type').value === 'VALUABLE' - ? 'MANUAL' - : this.activityForm.get('dataSource').value, fee: this.activityForm.get('fee').value, quantity: this.activityForm.get('quantity').value, symbol: From 7bcd18711a3cae44636f6877a3005faf5c97d193 Mon Sep 17 00:00:00 2001 From: Vic Chen Date: Tue, 24 Feb 2026 03:17:31 +0800 Subject: [PATCH 11/20] Task/improve language localization for ZH 20260217 (#6348) * Improve language localization for ZH * Update changelog --- CHANGELOG.md | 4 ++++ apps/client/src/locales/messages.zh.xlf | 8 ++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 160b4cbf0..b87e4822d 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 + +- Improved the language localization for Chinese (`zh`) + ### Fixed - Fixed an issue when creating activities of type `FEE`, `INTEREST` or `LIABILITY` diff --git a/apps/client/src/locales/messages.zh.xlf b/apps/client/src/locales/messages.zh.xlf index 9feb0e546..67007cdef 100644 --- a/apps/client/src/locales/messages.zh.xlf +++ b/apps/client/src/locales/messages.zh.xlf @@ -7891,7 +7891,7 @@ Fee Ratio (legacy) - 费率 + 费率(旧版) apps/client/src/app/pages/i18n/i18n-page.html 152 @@ -7915,7 +7915,7 @@ Fee Ratio - Fee Ratio + 费率 apps/client/src/app/pages/i18n/i18n-page.html 161 @@ -7923,7 +7923,7 @@ The fees do exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) - The fees do exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) + 费用已超过您总投资金额的 ${thresholdMax}%(${feeRatio}%) apps/client/src/app/pages/i18n/i18n-page.html 163 @@ -7931,7 +7931,7 @@ The fees do not exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) - The fees do not exceed ${thresholdMax}% of your total investment volume (${feeRatio}%) + 费用未超过您总投资金额的 ${thresholdMax}%(${feeRatio}%) apps/client/src/app/pages/i18n/i18n-page.html 167 From 236e1aacf56e9d876f17ebdcef4328a4141fa034 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 23 Feb 2026 20:29:07 +0100 Subject: [PATCH 12/20] Task/upgrade nestjs to version 11.1.14 (#6379) * Upgrade nestjs to version 11.1.14 * Update changelog --- CHANGELOG.md | 1 + package-lock.json | 223 +++++++++++++++++++--------------------------- package.json | 16 ++-- 3 files changed, 100 insertions(+), 140 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b87e4822d..a482c10e8 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 - Improved the language localization for Chinese (`zh`) +- Upgraded `nestjs` from version `11.1.8` to `11.1.14` ### Fixed diff --git a/package-lock.json b/package-lock.json index 7a1ebfa67..9d022919b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,15 +27,15 @@ "@ionic/angular": "8.7.8", "@keyv/redis": "4.4.0", "@nestjs/bull": "11.0.4", - "@nestjs/cache-manager": "3.0.1", - "@nestjs/common": "11.1.8", - "@nestjs/config": "4.0.2", - "@nestjs/core": "11.1.8", + "@nestjs/cache-manager": "3.1.0", + "@nestjs/common": "11.1.14", + "@nestjs/config": "4.0.3", + "@nestjs/core": "11.1.14", "@nestjs/event-emitter": "3.0.1", - "@nestjs/jwt": "11.0.1", + "@nestjs/jwt": "11.0.2", "@nestjs/passport": "11.0.5", - "@nestjs/platform-express": "11.1.8", - "@nestjs/schedule": "6.0.1", + "@nestjs/platform-express": "11.1.14", + "@nestjs/schedule": "6.1.1", "@nestjs/serve-static": "5.0.4", "@openrouter/ai-sdk-provider": "0.7.2", "@prisma/client": "6.19.0", @@ -107,7 +107,7 @@ "@eslint/eslintrc": "3.3.1", "@eslint/js": "9.35.0", "@nestjs/schematics": "11.0.9", - "@nestjs/testing": "11.1.8", + "@nestjs/testing": "11.1.14", "@nx/angular": "22.4.5", "@nx/eslint-plugin": "22.4.5", "@nx/jest": "22.4.5", @@ -3617,6 +3617,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@borewit/text-codec": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.1.tgz", + "integrity": "sha512-k7vvKPbf7J2fZ5klGRD9AeKfUvojuZIQ3BT5u7Jfv+puwXkUBUT5PVyMDfJZpy30CBDXGMgw7fguK/lpOMBvgw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/@braintree/sanitize-url": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.1.tgz", @@ -7531,9 +7541,9 @@ } }, "node_modules/@nestjs/cache-manager": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@nestjs/cache-manager/-/cache-manager-3.0.1.tgz", - "integrity": "sha512-4UxTnR0fsmKL5YDalU2eLFVnL+OBebWUpX+hEduKGncrVKH4PPNoiRn1kXyOCjmzb0UvWgqubpssNouc8e0MCw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@nestjs/cache-manager/-/cache-manager-3.1.0.tgz", + "integrity": "sha512-pEIqYZrBcE8UdkJmZRduurvoUfdU+3kRPeO1R2muiMbZnRuqlki5klFFNllO9LyYWzrx98bd1j0PSPKSJk1Wbw==", "license": "MIT", "peerDependencies": { "@nestjs/common": "^9.0.0 || ^10.0.0 || ^11.0.0", @@ -7544,12 +7554,12 @@ } }, "node_modules/@nestjs/common": { - "version": "11.1.8", - "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.8.tgz", - "integrity": "sha512-bbsOqwld/GdBfiRNc4nnjyWWENDEicq4SH+R5AuYatvf++vf1x5JIsHB1i1KtfZMD3eRte0D4K9WXuAYil6XAg==", + "version": "11.1.14", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.14.tgz", + "integrity": "sha512-IN/tlqd7Nl9gl6f0jsWEuOrQDaCI9vHzxv0fisHysfBQzfQIkqlv5A7w4Qge02BUQyczXT9HHPgHtWHCxhjRng==", "license": "MIT", "dependencies": { - "file-type": "21.0.0", + "file-type": "21.3.0", "iterare": "1.2.1", "load-esm": "1.0.3", "tslib": "2.8.1", @@ -7575,57 +7585,24 @@ } }, "node_modules/@nestjs/config": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-4.0.2.tgz", - "integrity": "sha512-McMW6EXtpc8+CwTUwFdg6h7dYcBUpH5iUILCclAsa+MbCEvC9ZKu4dCHRlJqALuhjLw97pbQu62l4+wRwGeZqA==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-4.0.3.tgz", + "integrity": "sha512-FQ3M3Ohqfl+nHAn5tp7++wUQw0f2nAk+SFKe8EpNRnIifPqvfJP6JQxPKtFLMOHbyer4X646prFG4zSRYEssQQ==", "license": "MIT", "dependencies": { - "dotenv": "16.4.7", - "dotenv-expand": "12.0.1", - "lodash": "4.17.21" + "dotenv": "17.2.3", + "dotenv-expand": "12.0.3", + "lodash": "4.17.23" }, "peerDependencies": { "@nestjs/common": "^10.0.0 || ^11.0.0", "rxjs": "^7.1.0" } }, - "node_modules/@nestjs/config/node_modules/dotenv": { - "version": "16.4.7", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", - "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/@nestjs/config/node_modules/dotenv-expand": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-12.0.1.tgz", - "integrity": "sha512-LaKRbou8gt0RNID/9RoI+J2rvXsBRPMV7p+ElHlPhcSARbCPDYcYG2s1TIzAfWv4YSgyY5taidWzzs31lNV3yQ==", - "license": "BSD-2-Clause", - "dependencies": { - "dotenv": "^16.4.5" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/@nestjs/config/node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "license": "MIT" - }, "node_modules/@nestjs/core": { - "version": "11.1.8", - "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.8.tgz", - "integrity": "sha512-7riWfmTmMhCJHZ5ZiaG+crj4t85IPCq/wLRuOUSigBYyFT2JZj0lVHtAdf4Davp9ouNI8GINBDt9h9b5Gz9nTw==", + "version": "11.1.14", + "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.14.tgz", + "integrity": "sha512-7OXPPMoDr6z+5NkoQKu4hOhfjz/YYqM3bNilPqv1WVFWrzSmuNXxvhbX69YMmNmRYascPXiwESqf5jJdjKXEww==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -7677,13 +7654,13 @@ } }, "node_modules/@nestjs/jwt": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/@nestjs/jwt/-/jwt-11.0.1.tgz", - "integrity": "sha512-HXSsc7SAnCnjA98TsZqrE7trGtHDnYXWp4Ffy6LwSmck1QvbGYdMzBquXofX5l6tIRpeY4Qidl2Ti2CVG77Pdw==", + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/@nestjs/jwt/-/jwt-11.0.2.tgz", + "integrity": "sha512-rK8aE/3/Ma45gAWfCksAXUNbOoSOUudU0Kn3rT39htPF7wsYXtKfjALKeKKJbFrIWbLjsbqfXX5bIJNvgBugGA==", "license": "MIT", "dependencies": { "@types/jsonwebtoken": "9.0.10", - "jsonwebtoken": "9.0.2" + "jsonwebtoken": "9.0.3" }, "peerDependencies": { "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0" @@ -7700,13 +7677,13 @@ } }, "node_modules/@nestjs/platform-express": { - "version": "11.1.8", - "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.8.tgz", - "integrity": "sha512-rL6pZH9BW7BnL5X2eWbJMtt86uloAKjFgyY5+L2UkizgfEp7rgAs0+Z1z0BcW2Pgu5+q8O7RKPNyHJ/9ZNz/ZQ==", + "version": "11.1.14", + "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.14.tgz", + "integrity": "sha512-Fs+/j+mBSBSXErOQJ/YdUn/HqJGSJ4pGfiJyYOyz04l42uNVnqEakvu1kXLbxMabR6vd6/h9d6Bi4tso9p7o4Q==", "license": "MIT", "dependencies": { - "cors": "2.8.5", - "express": "5.1.0", + "cors": "2.8.6", + "express": "5.2.1", "multer": "2.0.2", "path-to-regexp": "8.3.0", "tslib": "2.8.1" @@ -7721,12 +7698,12 @@ } }, "node_modules/@nestjs/schedule": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@nestjs/schedule/-/schedule-6.0.1.tgz", - "integrity": "sha512-v3yO6cSPAoBSSyH67HWnXHzuhPhSNZhRmLY38JvCt2sqY8sPMOODpcU1D79iUMFf7k16DaMEbL4Mgx61ZhiC8Q==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@nestjs/schedule/-/schedule-6.1.1.tgz", + "integrity": "sha512-kQl1RRgi02GJ0uaUGCrXHCcwISsCsJDciCKe38ykJZgnAeeoeVWs8luWtBo4AqAAXm4nS5K8RlV0smHUJ4+2FA==", "license": "MIT", "dependencies": { - "cron": "4.3.3" + "cron": "4.4.0" }, "peerDependencies": { "@nestjs/common": "^10.0.0 || ^11.0.0", @@ -7910,9 +7887,9 @@ } }, "node_modules/@nestjs/testing": { - "version": "11.1.8", - "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-11.1.8.tgz", - "integrity": "sha512-E6K+0UTKztcPxJzLnQa7S34lFjZbrj3Z1r7c5y5WDrL1m5HD1H4AeyBhicHgdaFmxjLAva2bq0sYKy/S7cdeYA==", + "version": "11.1.14", + "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-11.1.14.tgz", + "integrity": "sha512-cQxX0ronsTbpfHz8/LYOVWXxoTxv6VoxrnuZoQaVX7QV2PSMqxWE7/9jSQR0GcqAFUEmFP34c6EJqfkjfX/k4Q==", "dev": true, "license": "MIT", "dependencies": { @@ -12408,14 +12385,13 @@ } }, "node_modules/@tokenizer/inflate": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.2.7.tgz", - "integrity": "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", + "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==", "license": "MIT", "dependencies": { - "debug": "^4.4.0", - "fflate": "^0.8.2", - "token-types": "^6.0.0" + "debug": "^4.4.3", + "token-types": "^6.1.1" }, "engines": { "node": ">=18" @@ -16984,9 +16960,9 @@ "license": "MIT" }, "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", "license": "MIT", "dependencies": { "object-assign": "^4", @@ -16994,6 +16970,10 @@ }, "engines": { "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/corser": { @@ -17073,9 +17053,9 @@ "license": "MIT" }, "node_modules/cron": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/cron/-/cron-4.3.3.tgz", - "integrity": "sha512-B/CJj5yL3sjtlun6RtYHvoSB26EmQ2NUmhq9ZiJSyKIM4K/fqfh9aelDFlIayD2YMeFZqWLi9hHV+c+pq2Djkw==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/cron/-/cron-4.4.0.tgz", + "integrity": "sha512-fkdfq+b+AHI4cKdhZlppHveI/mgz2qpiYxcm+t5E5TsxX7QrLS1VE0+7GENEk9z0EeGPcpSciGv6ez24duWhwQ==", "license": "MIT", "dependencies": { "@types/luxon": "~3.7.0", @@ -17083,6 +17063,10 @@ }, "engines": { "node": ">=18.x" + }, + "funding": { + "type": "ko-fi", + "url": "https://ko-fi.com/intcreator" } }, "node_modules/cron-parser": { @@ -19852,18 +19836,19 @@ "license": "Apache-2.0" }, "node_modules/express": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", - "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", "dependencies": { "accepts": "^2.0.0", - "body-parser": "^2.2.0", + "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", + "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", @@ -20129,12 +20114,6 @@ "filenamify-url": "2.1.2" } }, - "node_modules/fflate": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", - "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", - "license": "MIT" - }, "node_modules/figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", @@ -20175,14 +20154,14 @@ } }, "node_modules/file-type": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.0.0.tgz", - "integrity": "sha512-ek5xNX2YBYlXhiUXui3D/BXa3LdqPmoLJ7rqEx2bKJ7EAUEfmXgW0Das7Dc6Nr9MvqaOnIqiPV0mZk/r/UpNAg==", + "version": "21.3.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.0.tgz", + "integrity": "sha512-8kPJMIGz1Yt/aPEwOsrR97ZyZaD1Iqm8PClb1nYFclUCkBi0Ma5IsYNQzvSFS9ib51lWyIw5mIT9rWzI/xjpzA==", "license": "MIT", "dependencies": { - "@tokenizer/inflate": "^0.2.7", - "strtok3": "^10.2.2", - "token-types": "^6.0.0", + "@tokenizer/inflate": "^0.4.1", + "strtok3": "^10.3.4", + "token-types": "^6.1.1", "uint8array-extras": "^1.4.0" }, "engines": { @@ -24592,12 +24571,12 @@ } }, "node_modules/jsonwebtoken": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", - "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", "license": "MIT", "dependencies": { - "jws": "^3.2.2", + "jws": "^4.0.1", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", @@ -24613,27 +24592,6 @@ "npm": ">=6" } }, - "node_modules/jsonwebtoken/node_modules/jwa": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", - "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", - "license": "MIT", - "dependencies": { - "buffer-equal-constant-time": "^1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/jsonwebtoken/node_modules/jws": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.3.tgz", - "integrity": "sha512-byiJ0FLRdLdSVSReO/U4E7RoEyOCKnEnEPMjq3HxWtvzLsV08/i5RQKsFVNkCldrCaPr2vDNAOMsfs8T/Hze7g==", - "license": "MIT", - "dependencies": { - "jwa": "^1.4.2", - "safe-buffer": "^5.0.1" - } - }, "node_modules/jsonwebtoken/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -32900,11 +32858,12 @@ } }, "node_modules/token-types": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.0.4.tgz", - "integrity": "sha512-MD9MjpVNhVyH4fyd5rKphjvt/1qj+PtQUz65aFqAZA6XniWAuSFRjLk3e2VALEFlh9OwBpXUN7rfeqSnT/Fmkw==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz", + "integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==", "license": "MIT", "dependencies": { + "@borewit/text-codec": "^0.2.1", "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" }, @@ -33690,9 +33649,9 @@ "license": "MIT" }, "node_modules/uint8array-extras": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.4.0.tgz", - "integrity": "sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", + "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", "license": "MIT", "engines": { "node": ">=18" diff --git a/package.json b/package.json index 2bf5eebe0..5f80c3627 100644 --- a/package.json +++ b/package.json @@ -72,15 +72,15 @@ "@ionic/angular": "8.7.8", "@keyv/redis": "4.4.0", "@nestjs/bull": "11.0.4", - "@nestjs/cache-manager": "3.0.1", - "@nestjs/common": "11.1.8", - "@nestjs/config": "4.0.2", - "@nestjs/core": "11.1.8", + "@nestjs/cache-manager": "3.1.0", + "@nestjs/common": "11.1.14", + "@nestjs/config": "4.0.3", + "@nestjs/core": "11.1.14", "@nestjs/event-emitter": "3.0.1", - "@nestjs/jwt": "11.0.1", + "@nestjs/jwt": "11.0.2", "@nestjs/passport": "11.0.5", - "@nestjs/platform-express": "11.1.8", - "@nestjs/schedule": "6.0.1", + "@nestjs/platform-express": "11.1.14", + "@nestjs/schedule": "6.1.1", "@nestjs/serve-static": "5.0.4", "@openrouter/ai-sdk-provider": "0.7.2", "@prisma/client": "6.19.0", @@ -152,7 +152,7 @@ "@eslint/eslintrc": "3.3.1", "@eslint/js": "9.35.0", "@nestjs/schematics": "11.0.9", - "@nestjs/testing": "11.1.8", + "@nestjs/testing": "11.1.14", "@nx/angular": "22.4.5", "@nx/eslint-plugin": "22.4.5", "@nx/jest": "22.4.5", From cc92ecf62a014b27146338bb7a20ed6eeb525fba Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 23 Feb 2026 20:30:53 +0100 Subject: [PATCH 13/20] Release 2.243.0 (#6384) --- 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 a482c10e8..f806a260e 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.243.0 - 2026-02-23 ### Changed diff --git a/package-lock.json b/package-lock.json index 9d022919b..fadeca52d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ghostfolio", - "version": "2.242.0", + "version": "2.243.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ghostfolio", - "version": "2.242.0", + "version": "2.243.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/package.json b/package.json index 5f80c3627..ad615c3a0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ghostfolio", - "version": "2.242.0", + "version": "2.243.0", "homepage": "https://ghostfol.io", "license": "AGPL-3.0", "repository": "https://github.com/ghostfolio/ghostfolio", From 90cc7af87c10a4bfc9eb00b218b2046b246c3aa6 Mon Sep 17 00:00:00 2001 From: Kenrick Tandrian <60643640+KenTandrian@users.noreply.github.com> Date: Tue, 24 Feb 2026 23:42:41 +0400 Subject: [PATCH 14/20] Task/improve type safety in fire calculator component (#6385) * fix(lib): resolve typescript errors * fix(lib): make unsubscribeSubject readonly * feat(lib): migrate outputs to signals * feat(lib): implement takeUntilDestroyed * feat(lib): add linebreak --- .../fire-calculator.component.ts | 128 ++++++++++-------- 1 file changed, 72 insertions(+), 56 deletions(-) diff --git a/libs/ui/src/lib/fire-calculator/fire-calculator.component.ts b/libs/ui/src/lib/fire-calculator/fire-calculator.component.ts index 7461f6729..8b472fc47 100644 --- a/libs/ui/src/lib/fire-calculator/fire-calculator.component.ts +++ b/libs/ui/src/lib/fire-calculator/fire-calculator.component.ts @@ -13,13 +13,13 @@ import { ChangeDetectorRef, Component, ElementRef, - EventEmitter, Input, OnChanges, OnDestroy, - Output, - ViewChild + ViewChild, + output } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormBuilder, FormControl, @@ -55,9 +55,9 @@ import { startOfMonth, sub } from 'date-fns'; -import { isNumber } from 'lodash'; +import { isNil, isNumber } from 'lodash'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; -import { Subject, debounceTime, takeUntil } from 'rxjs'; +import { debounceTime } from 'rxjs'; import { FireCalculatorService } from './fire-calculator.service'; @@ -90,32 +90,31 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { @Input() retirementDate: Date; @Input() savingsRate = 0; - @Output() annualInterestRateChanged = new EventEmitter(); - @Output() calculationCompleted = - new EventEmitter(); - @Output() projectedTotalAmountChanged = new EventEmitter(); - @Output() retirementDateChanged = new EventEmitter(); - @Output() savingsRateChanged = new EventEmitter(); - @ViewChild('chartCanvas') chartCanvas: ElementRef; public calculatorForm = this.formBuilder.group({ - annualInterestRate: new FormControl(undefined), - paymentPerPeriod: new FormControl(undefined), - principalInvestmentAmount: new FormControl(undefined), - projectedTotalAmount: new FormControl(undefined), - retirementDate: new FormControl(undefined) + annualInterestRate: new FormControl(null), + paymentPerPeriod: new FormControl(null), + principalInvestmentAmount: new FormControl(null), + projectedTotalAmount: new FormControl(null), + retirementDate: new FormControl(null) }); public chart: Chart<'bar'>; public isLoading = true; public minDate = addDays(new Date(), 1); public periodsToRetire = 0; + protected readonly annualInterestRateChanged = output(); + protected readonly calculationCompleted = + output(); + protected readonly projectedTotalAmountChanged = output(); + protected readonly retirementDateChanged = output(); + protected readonly savingsRateChanged = output(); + private readonly CONTRIBUTION_PERIOD = 12; private readonly DEFAULT_RETIREMENT_DATE = startOfMonth( addYears(new Date(), 10) ); - private unsubscribeSubject = new Subject(); public constructor( private changeDetectorRef: ChangeDetectorRef, @@ -131,46 +130,56 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { ); this.calculatorForm.valueChanges - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed()) .subscribe(() => { this.initialize(); }); this.calculatorForm.valueChanges - .pipe(debounceTime(500), takeUntil(this.unsubscribeSubject)) + .pipe(debounceTime(500), takeUntilDestroyed()) .subscribe(() => { const { projectedTotalAmount, retirementDate } = this.calculatorForm.getRawValue(); - this.calculationCompleted.emit({ - projectedTotalAmount, - retirementDate - }); + if (projectedTotalAmount !== null && retirementDate !== null) { + this.calculationCompleted.emit({ + projectedTotalAmount, + retirementDate + }); + } }); this.calculatorForm .get('annualInterestRate') - .valueChanges.pipe(debounceTime(500), takeUntil(this.unsubscribeSubject)) + ?.valueChanges.pipe(debounceTime(500), takeUntilDestroyed()) .subscribe((annualInterestRate) => { - this.annualInterestRateChanged.emit(annualInterestRate); + if (annualInterestRate !== null) { + this.annualInterestRateChanged.emit(annualInterestRate); + } }); this.calculatorForm .get('paymentPerPeriod') - .valueChanges.pipe(debounceTime(500), takeUntil(this.unsubscribeSubject)) + ?.valueChanges.pipe(debounceTime(500), takeUntilDestroyed()) .subscribe((savingsRate) => { - this.savingsRateChanged.emit(savingsRate); + if (savingsRate !== null) { + this.savingsRateChanged.emit(savingsRate); + } }); this.calculatorForm .get('projectedTotalAmount') - .valueChanges.pipe(debounceTime(500), takeUntil(this.unsubscribeSubject)) + ?.valueChanges.pipe(debounceTime(500), takeUntilDestroyed()) .subscribe((projectedTotalAmount) => { - this.projectedTotalAmountChanged.emit(projectedTotalAmount); + if (projectedTotalAmount !== null) { + this.projectedTotalAmountChanged.emit(projectedTotalAmount); + } }); this.calculatorForm .get('retirementDate') - .valueChanges.pipe(debounceTime(500), takeUntil(this.unsubscribeSubject)) + ?.valueChanges.pipe(debounceTime(500), takeUntilDestroyed()) .subscribe((retirementDate) => { - this.retirementDateChanged.emit(retirementDate); + if (retirementDate !== null) { + this.retirementDateChanged.emit(retirementDate); + } }); } @@ -196,11 +205,11 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { this.calculatorForm.patchValue( { annualInterestRate: - this.calculatorForm.get('annualInterestRate').value, + this.calculatorForm.get('annualInterestRate')?.value, paymentPerPeriod: this.getPMT(), principalInvestmentAmount: this.calculatorForm.get( 'principalInvestmentAmount' - ).value, + )?.value, projectedTotalAmount: Math.round(this.getProjectedTotalAmount()) || 0, retirementDate: @@ -210,7 +219,7 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { emitEvent: false } ); - this.calculatorForm.get('principalInvestmentAmount').disable(); + this.calculatorForm.get('principalInvestmentAmount')?.disable(); this.changeDetectorRef.markForCheck(); }); @@ -219,42 +228,43 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { if (this.hasPermissionToUpdateUserSettings === true) { this.calculatorForm .get('annualInterestRate') - .enable({ emitEvent: false }); - this.calculatorForm.get('paymentPerPeriod').enable({ emitEvent: false }); + ?.enable({ emitEvent: false }); + this.calculatorForm.get('paymentPerPeriod')?.enable({ emitEvent: false }); this.calculatorForm .get('projectedTotalAmount') - .enable({ emitEvent: false }); + ?.enable({ emitEvent: false }); } else { this.calculatorForm .get('annualInterestRate') - .disable({ emitEvent: false }); - this.calculatorForm.get('paymentPerPeriod').disable({ emitEvent: false }); + ?.disable({ emitEvent: false }); + this.calculatorForm + .get('paymentPerPeriod') + ?.disable({ emitEvent: false }); this.calculatorForm .get('projectedTotalAmount') - .disable({ emitEvent: false }); + ?.disable({ emitEvent: false }); } - this.calculatorForm.get('retirementDate').disable({ emitEvent: false }); + this.calculatorForm.get('retirementDate')?.disable({ emitEvent: false }); } public setMonthAndYear( normalizedMonthAndYear: Date, datepicker: MatDatepicker ) { - const retirementDate = this.calculatorForm.get('retirementDate').value; + const retirementDate = + this.calculatorForm.get('retirementDate')?.value ?? + this.DEFAULT_RETIREMENT_DATE; const newRetirementDate = setMonth( setYear(retirementDate, normalizedMonthAndYear.getFullYear()), normalizedMonthAndYear.getMonth() ); - this.calculatorForm.get('retirementDate').setValue(newRetirementDate); + this.calculatorForm.get('retirementDate')?.setValue(newRetirementDate); datepicker.close(); } public ngOnDestroy() { this.chart?.destroy(); - - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); } private initialize() { @@ -288,8 +298,6 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { return `Total: ${new Intl.NumberFormat(this.locale, { currency: this.currency, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore: Only supported from ES2020 or later currencyDisplay: 'code', style: 'currency' }).format(totalAmount)}`; @@ -426,12 +434,16 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { } private getPeriodsToRetire(): number { - if (this.calculatorForm.get('projectedTotalAmount').value) { + const projectedTotalAmount = this.calculatorForm.get( + 'projectedTotalAmount' + )?.value; + + if (projectedTotalAmount) { let periods = this.fireCalculatorService.calculatePeriodsToRetire({ P: this.getP(), PMT: this.getPMT(), r: this.getR(), - totalAmount: this.calculatorForm.get('projectedTotalAmount').value + totalAmount: projectedTotalAmount }); if (periods === Infinity) { @@ -453,12 +465,16 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { } private getPMT() { - return this.calculatorForm.get('paymentPerPeriod').value; + return this.calculatorForm.get('paymentPerPeriod')?.value ?? 0; } private getProjectedTotalAmount() { - if (this.calculatorForm.get('projectedTotalAmount').value) { - return this.calculatorForm.get('projectedTotalAmount').value; + const projectedTotalAmount = this.calculatorForm.get( + 'projectedTotalAmount' + )?.value; + + if (!isNil(projectedTotalAmount)) { + return projectedTotalAmount; } const { totalAmount } = @@ -473,12 +489,12 @@ export class GfFireCalculatorComponent implements OnChanges, OnDestroy { } private getR() { - return this.calculatorForm.get('annualInterestRate').value / 100; + return (this.calculatorForm.get('annualInterestRate')?.value ?? 0) / 100; } private getRetirementDate(): Date { if (this.periodsToRetire === Number.MAX_SAFE_INTEGER) { - return undefined; + return this.DEFAULT_RETIREMENT_DATE; } const monthsToRetire = this.periodsToRetire % 12; From 2e01c121e451406b0b4c47e76c84de627a9bd1b1 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Tue, 24 Feb 2026 20:43:17 +0100 Subject: [PATCH 15/20] Task/remove unused OnDestroy hook in resources page component (#6367) * Remove unused OnDestroy hook --- .../src/app/pages/resources/resources-page.component.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/apps/client/src/app/pages/resources/resources-page.component.ts b/apps/client/src/app/pages/resources/resources-page.component.ts index 9db996f57..c25ef00d6 100644 --- a/apps/client/src/app/pages/resources/resources-page.component.ts +++ b/apps/client/src/app/pages/resources/resources-page.component.ts @@ -13,7 +13,6 @@ import { readerOutline } from 'ionicons/icons'; import { DeviceDetectorService } from 'ngx-device-detector'; -import { Subject } from 'rxjs'; @Component({ host: { class: 'page has-tabs' }, @@ -47,8 +46,6 @@ export class ResourcesPageComponent implements OnInit { } ]; - private unsubscribeSubject = new Subject(); - public constructor(private deviceService: DeviceDetectorService) { addIcons({ bookOutline, libraryOutline, newspaperOutline, readerOutline }); } @@ -56,9 +53,4 @@ export class ResourcesPageComponent implements OnInit { public ngOnInit() { this.deviceType = this.deviceService.getDeviceInfo().deviceType; } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } } From 386a77c8f746f9413f0cdb837a4fe234059963cf Mon Sep 17 00:00:00 2001 From: Kenrick Tandrian <60643640+KenTandrian@users.noreply.github.com> Date: Wed, 25 Feb 2026 22:15:06 +0400 Subject: [PATCH 16/20] Task/improve type safety in assistant components (#6396) * feat(lib): resolve typescript errors in assistant * feat(lib): resolve typescript errors in assistant list item * feat(lib): implement output signals * fix(lint): resolve warnings * fix(lib): implement inject function * fix(lib): handle errors in html files --- .../assistant-list-item.component.ts | 24 +++-- .../assistant-list-item.html | 2 +- .../src/lib/assistant/assistant.component.ts | 102 +++++++++--------- libs/ui/src/lib/assistant/assistant.html | 2 +- .../portfolio-filter-form-value.interface.ts | 8 +- 5 files changed, 70 insertions(+), 68 deletions(-) diff --git a/libs/ui/src/lib/assistant/assistant-list-item/assistant-list-item.component.ts b/libs/ui/src/lib/assistant/assistant-list-item/assistant-list-item.component.ts index c2ad2462e..05750cea4 100644 --- a/libs/ui/src/lib/assistant/assistant-list-item/assistant-list-item.component.ts +++ b/libs/ui/src/lib/assistant/assistant-list-item/assistant-list-item.component.ts @@ -7,12 +7,12 @@ import { ChangeDetectorRef, Component, ElementRef, - EventEmitter, HostBinding, Input, OnChanges, - Output, - ViewChild + ViewChild, + inject, + output } from '@angular/core'; import { Params, RouterModule } from '@angular/router'; @@ -33,21 +33,23 @@ export class GfAssistantListItemComponent implements FocusableOption, OnChanges { @HostBinding('attr.tabindex') tabindex = -1; - @HostBinding('class.has-focus') get getHasFocus() { - return this.hasFocus; - } @Input() item: SearchResultItem; - @Output() clicked = new EventEmitter(); - - @ViewChild('link') public linkElement: ElementRef; + @ViewChild('link') public linkElement: ElementRef; public hasFocus = false; public queryParams: Params; public routerLink: string[]; - public constructor(private changeDetectorRef: ChangeDetectorRef) {} + protected readonly clicked = output(); + + private readonly changeDetectorRef = inject(ChangeDetectorRef); + + @HostBinding('class.has-focus') + public get getHasFocus() { + return this.hasFocus; + } public ngOnChanges() { if (this.item?.mode === SearchMode.ACCOUNT) { @@ -65,7 +67,7 @@ export class GfAssistantListItemComponent }; this.routerLink = - internalRoutes.adminControl.subRoutes.marketData.routerLink; + internalRoutes.adminControl.subRoutes?.marketData.routerLink ?? []; } else if (this.item?.mode === SearchMode.HOLDING) { this.queryParams = { dataSource: this.item.dataSource, diff --git a/libs/ui/src/lib/assistant/assistant-list-item/assistant-list-item.html b/libs/ui/src/lib/assistant/assistant-list-item/assistant-list-item.html index fd2c4011d..36179b719 100644 --- a/libs/ui/src/lib/assistant/assistant-list-item/assistant-list-item.html +++ b/libs/ui/src/lib/assistant/assistant-list-item/assistant-list-item.html @@ -8,7 +8,7 @@ @if (item && isAsset(item)) {
{{ item?.symbol | gfSymbol }} · {{ item?.currency }} + >{{ item?.symbol ?? '' | gfSymbol }} · {{ item?.currency }} @if (item?.assetSubClassString) { · {{ item.assetSubClassString }} } diff --git a/libs/ui/src/lib/assistant/assistant.component.ts b/libs/ui/src/lib/assistant/assistant.component.ts index 2b0216613..1c67e4fa2 100644 --- a/libs/ui/src/lib/assistant/assistant.component.ts +++ b/libs/ui/src/lib/assistant/assistant.component.ts @@ -12,16 +12,15 @@ import { ChangeDetectorRef, Component, ElementRef, - EventEmitter, HostListener, Input, OnChanges, OnDestroy, OnInit, - Output, QueryList, ViewChild, - ViewChildren + ViewChildren, + output } from '@angular/core'; import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; @@ -86,37 +85,7 @@ import { templateUrl: './assistant.html' }) export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { - @HostListener('document:keydown', ['$event']) onKeydown( - event: KeyboardEvent - ) { - if (!this.isOpen) { - return; - } - - if (event.key === 'ArrowDown' || event.key === 'ArrowUp') { - for (const item of this.assistantListItems) { - item.removeFocus(); - } - - this.keyManager.onKeydown(event); - - const currentAssistantListItem = this.getCurrentAssistantListItem(); - - if (currentAssistantListItem?.linkElement) { - currentAssistantListItem.linkElement.nativeElement?.scrollIntoView({ - behavior: 'smooth', - block: 'center' - }); - } - } else if (event.key === 'Enter') { - const currentAssistantListItem = this.getCurrentAssistantListItem(); - - if (currentAssistantListItem?.linkElement) { - currentAssistantListItem.linkElement.nativeElement?.click(); - event.stopPropagation(); - } - } - } + public static readonly SEARCH_RESULTS_DEFAULT_LIMIT = 5; @Input() deviceType: string; @Input() hasPermissionToAccessAdminControl: boolean; @@ -124,21 +93,16 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { @Input() hasPermissionToChangeFilters: boolean; @Input() user: User; - @Output() closed = new EventEmitter(); - @Output() dateRangeChanged = new EventEmitter(); - @Output() filtersChanged = new EventEmitter(); - @ViewChild('menuTrigger') menuTriggerElement: MatMenuTrigger; - @ViewChild('search', { static: true }) searchElement: ElementRef; + @ViewChild('search', { static: true }) + searchElement: ElementRef; @ViewChildren(GfAssistantListItemComponent) assistantListItems: QueryList; - public static readonly SEARCH_RESULTS_DEFAULT_LIMIT = 5; - public accounts: AccountWithPlatform[] = []; public assetClasses: Filter[] = []; - public dateRangeFormControl = new FormControl(undefined); + public dateRangeFormControl = new FormControl(null); public dateRangeOptions: DateRangeOption[] = []; public holdings: PortfolioPosition[] = []; public isLoading = { @@ -166,6 +130,10 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { }; public tags: Filter[] = []; + protected readonly closed = output(); + protected readonly dateRangeChanged = output(); + protected readonly filtersChanged = output(); + private readonly PRESELECTION_DELAY = 100; private filterTypes: Filter['type'][] = [ @@ -188,6 +156,37 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { addIcons({ closeCircleOutline, closeOutline, searchOutline }); } + @HostListener('document:keydown', ['$event']) + public onKeydown(event: KeyboardEvent) { + if (!this.isOpen) { + return; + } + + if (event.key === 'ArrowDown' || event.key === 'ArrowUp') { + for (const item of this.assistantListItems) { + item.removeFocus(); + } + + this.keyManager.onKeydown(event); + + const currentAssistantListItem = this.getCurrentAssistantListItem(); + + if (currentAssistantListItem?.linkElement) { + currentAssistantListItem.linkElement.nativeElement?.scrollIntoView({ + behavior: 'smooth', + block: 'center' + }); + } + } else if (event.key === 'Enter') { + const currentAssistantListItem = this.getCurrentAssistantListItem(); + + if (currentAssistantListItem?.linkElement) { + currentAssistantListItem.linkElement.nativeElement?.click(); + event.stopPropagation(); + } + } + } + public ngOnInit() { this.assetClasses = Object.keys(AssetClass).map((assetClass) => { return { @@ -482,7 +481,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { .subscribe(({ holdings }) => { this.holdings = holdings .filter(({ assetSubClass }) => { - return !['CASH'].includes(assetSubClass); + return assetSubClass && !['CASH'].includes(assetSubClass); }) .sort((a, b) => { return a.name?.localeCompare(b.name); @@ -499,23 +498,23 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { this.filtersChanged.emit([ { - id: filterValue?.account, + id: filterValue?.account ?? '', type: 'ACCOUNT' }, { - id: filterValue?.assetClass, + id: filterValue?.assetClass ?? '', type: 'ASSET_CLASS' }, { - id: filterValue?.holding?.dataSource, + id: filterValue?.holding?.dataSource ?? '', type: 'DATA_SOURCE' }, { - id: filterValue?.holding?.symbol, + id: filterValue?.holding?.symbol ?? '', type: 'SYMBOL' }, { - id: filterValue?.tag, + id: filterValue?.tag ?? '', type: 'TAG' } ]); @@ -541,7 +540,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { this.filterTypes.map((type) => { return { type, - id: null + id: '' }; }) ); @@ -673,7 +672,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { dataSource, name, symbol, - assetSubClassString: translate(assetSubClass), + assetSubClassString: translate(assetSubClass ?? ''), mode: SearchMode.ASSET_PROFILE as const }; } @@ -705,7 +704,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { dataSource, name, symbol, - assetSubClassString: translate(assetSubClass), + assetSubClassString: translate(assetSubClass ?? ''), mode: SearchMode.HOLDING as const }; } @@ -755,6 +754,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit { const symbol = this.user?.settings?.['filters.symbol']; const selectedHolding = this.holdings.find((holding) => { return ( + !!(dataSource && symbol) && getAssetProfileIdentifier({ dataSource: holding.dataSource, symbol: holding.symbol diff --git a/libs/ui/src/lib/assistant/assistant.html b/libs/ui/src/lib/assistant/assistant.html index 307269262..7e19833a9 100644 --- a/libs/ui/src/lib/assistant/assistant.html +++ b/libs/ui/src/lib/assistant/assistant.html @@ -186,7 +186,7 @@
Date: Wed, 25 Feb 2026 14:50:36 -0500 Subject: [PATCH 17/20] Bugfix/handle X-ray rule exception when market price is missing (#6397) * Handle X-ray rule exception when market price is missing * Update changelog --- CHANGELOG.md | 6 ++++++ apps/api/src/models/rule.ts | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f806a260e..6b1a1c978 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 an exception by adding a fallback for missing market price values on the _X-ray_ page + ## 2.243.0 - 2026-02-23 ### Changed diff --git a/apps/api/src/models/rule.ts b/apps/api/src/models/rule.ts index 9c27e0018..622375b5b 100644 --- a/apps/api/src/models/rule.ts +++ b/apps/api/src/models/rule.ts @@ -57,7 +57,7 @@ export abstract class Rule implements RuleInterface { previousValue + this.exchangeRateDataService.toCurrency( new Big(currentValue.quantity) - .mul(currentValue.marketPrice) + .mul(currentValue.marketPrice ?? 0) .toNumber(), currentValue.currency, baseCurrency From 4cf16b8c58440d2408ab4594a10fe6b17d06de3f Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Thu, 26 Feb 2026 17:26:00 +0100 Subject: [PATCH 18/20] Task/remove unused OnDestroy hook in license page component (#6380) * Remove unused OnDestroy hook --- .../pages/about/license/license-page.component.ts | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) 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 0dc5b2f51..d530d0418 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,6 +1,5 @@ -import { Component, OnDestroy } from '@angular/core'; +import { Component } from '@angular/core'; import { MarkdownModule } from 'ngx-markdown'; -import { Subject } from 'rxjs'; @Component({ imports: [MarkdownModule], @@ -8,11 +7,4 @@ import { Subject } from 'rxjs'; styleUrls: ['./license-page.scss'], templateUrl: './license-page.html' }) -export class GfLicensePageComponent implements OnDestroy { - private unsubscribeSubject = new Subject(); - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } -} +export class GfLicensePageComponent {} From 69740db29215439e44d0c0a139d7f8072d4cbe7c Mon Sep 17 00:00:00 2001 From: Kenrick Tandrian <60643640+KenTandrian@users.noreply.github.com> Date: Thu, 26 Feb 2026 20:26:21 +0400 Subject: [PATCH 19/20] Task/improve type safety in currency selector (#6402) * feat(lib): resolve typescript errors * feat(lib): make input a view child signal * feat(lib): remove unsubscribe subject * feat(lib): remove ngOnDestroy * feat(lib): make currencies an input signal * feat(lib): make formControlName an input signal --- .../currency-selector.component.ts | 90 +++++++++---------- 1 file changed, 42 insertions(+), 48 deletions(-) diff --git a/libs/ui/src/lib/currency-selector/currency-selector.component.ts b/libs/ui/src/lib/currency-selector/currency-selector.component.ts index 35a911716..7b6236fbb 100644 --- a/libs/ui/src/lib/currency-selector/currency-selector.component.ts +++ b/libs/ui/src/lib/currency-selector/currency-selector.component.ts @@ -4,13 +4,16 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, + DestroyRef, DoCheck, ElementRef, - Input, - OnDestroy, OnInit, - ViewChild + ViewChild, + inject, + input, + viewChild } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormControl, FormGroupDirective, @@ -21,15 +24,14 @@ import { import { MatAutocomplete, MatAutocompleteModule, - MatAutocompleteSelectedEvent + MatOption } from '@angular/material/autocomplete'; import { MatFormFieldControl, MatFormFieldModule } from '@angular/material/form-field'; import { MatInput, MatInputModule } from '@angular/material/input'; -import { Subject } from 'rxjs'; -import { map, startWith, takeUntil } from 'rxjs/operators'; +import { map, startWith } from 'rxjs/operators'; import { AbstractMatFormField } from '../shared/abstract-mat-form-field'; @@ -58,21 +60,19 @@ import { AbstractMatFormField } from '../shared/abstract-mat-form-field'; templateUrl: 'currency-selector.component.html' }) export class GfCurrencySelectorComponent - extends AbstractMatFormField - implements DoCheck, OnDestroy, OnInit + extends AbstractMatFormField + implements DoCheck, OnInit { - @Input() private currencies: string[] = []; - @Input() private formControlName: string; - - @ViewChild(MatInput) private input: MatInput; - @ViewChild('currencyAutocomplete') public currencyAutocomplete: MatAutocomplete; - public control = new FormControl(); + public readonly control = new FormControl(null); + public readonly currencies = input.required(); public filteredCurrencies: string[] = []; + public readonly formControlName = input.required(); - private unsubscribeSubject = new Subject(); + private readonly destroyRef = inject(DestroyRef); + private readonly input = viewChild.required(MatInput); public constructor( public readonly _elementRef: ElementRef, @@ -86,6 +86,19 @@ export class GfCurrencySelectorComponent this.controlType = 'currency-selector'; } + public get empty() { + return this.input().empty; + } + + public set value(value: string | null) { + this.control.setValue(value); + super.value = value; + } + + public focus() { + this.input().focus(); + } + public ngOnInit() { if (this.disabled) { this.control.disable(); @@ -94,17 +107,18 @@ export class GfCurrencySelectorComponent const formGroup = this.formGroupDirective.form; if (formGroup) { - const control = formGroup.get(this.formControlName); + const control = formGroup.get(this.formControlName()); if (control) { - this.value = this.currencies.find((value) => { - return value === control.value; - }); + this.value = + this.currencies().find((value) => { + return value === control.value; + }) ?? null; } } this.control.valueChanges - .pipe(takeUntil(this.unsubscribeSubject)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { if (super.value) { super.value = null; @@ -113,10 +127,10 @@ export class GfCurrencySelectorComponent this.control.valueChanges .pipe( - takeUntil(this.unsubscribeSubject), + takeUntilDestroyed(this.destroyRef), startWith(''), map((value) => { - return value ? this.filter(value) : this.currencies.slice(); + return value ? this.filter(value) : this.currencies().slice(); }) ) .subscribe((values) => { @@ -124,42 +138,22 @@ export class GfCurrencySelectorComponent }); } - public get empty() { - return this.input?.empty; - } - - public focus() { - this.input.focus(); - } - public ngDoCheck() { if (this.ngControl) { this.validateRequired(); - this.errorState = this.ngControl.invalid && this.ngControl.touched; + this.errorState = !!(this.ngControl.invalid && this.ngControl.touched); this.stateChanges.next(); } } - public onUpdateCurrency(event: MatAutocompleteSelectedEvent) { - super.value = event.option.value; - } - - public set value(value: string) { - this.control.setValue(value); - super.value = value; - } - - public ngOnDestroy() { - super.ngOnDestroy(); - - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); + public onUpdateCurrency({ option }: { option: MatOption }) { + super.value = option.value; } private filter(value: string) { - const filterValue = value?.toLowerCase(); + const filterValue = value.toLowerCase(); - return this.currencies.filter((currency) => { + return this.currencies().filter((currency) => { return currency.toLowerCase().startsWith(filterValue); }); } @@ -168,7 +162,7 @@ export class GfCurrencySelectorComponent const requiredCheck = super.required ? !super.value : false; if (requiredCheck) { - this.ngControl.control.setErrors({ invalidData: true }); + this.ngControl.control?.setErrors({ invalidData: true }); } } } From d4a0f48ca24ab925990853b59016b73fd2c1fa96 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Fri, 27 Feb 2026 19:50:04 +0100 Subject: [PATCH 20/20] Task/remove unused OnDestroy hook in demo page component (#6383) * Remove unused OnDestroy hook --- .../client/src/app/pages/demo/demo-page.component.ts | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) 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 5b94fd541..235805dcc 100644 --- a/apps/client/src/app/pages/demo/demo-page.component.ts +++ b/apps/client/src/app/pages/demo/demo-page.component.ts @@ -3,9 +3,8 @@ import { InfoItem } from '@ghostfolio/common/interfaces'; import { NotificationService } from '@ghostfolio/ui/notifications'; import { DataService } from '@ghostfolio/ui/services'; -import { Component, OnDestroy } from '@angular/core'; +import { Component } from '@angular/core'; import { Router } from '@angular/router'; -import { Subject } from 'rxjs'; @Component({ host: { class: 'page' }, @@ -13,11 +12,9 @@ import { Subject } from 'rxjs'; standalone: true, templateUrl: './demo-page.html' }) -export class GfDemoPageComponent implements OnDestroy { +export class GfDemoPageComponent { public info: InfoItem; - private unsubscribeSubject = new Subject(); - public constructor( private dataService: DataService, private notificationService: NotificationService, @@ -40,9 +37,4 @@ export class GfDemoPageComponent implements OnDestroy { this.router.navigate(['/']); } - - public ngOnDestroy() { - this.unsubscribeSubject.next(); - this.unsubscribeSubject.complete(); - } }