diff --git a/CHANGELOG.md b/CHANGELOG.md index e08cee33f..0c9d1783d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Hardened the endpoint of the public access for portfolio sharing by restricting it to public accesses - Improved the language localization by translating various tooltips across the application - Improved the language localization for Ukrainian (`uk`) +- Upgraded `yahoo-finance2` from version `3.14.3` to `3.15.4` ## 3.19.1 - 2026-07-03 diff --git a/apps/api/src/app/endpoints/public/public.controller.ts b/apps/api/src/app/endpoints/public/public.controller.ts index d06e3b5fe..f43e51f7b 100644 --- a/apps/api/src/app/endpoints/public/public.controller.ts +++ b/apps/api/src/app/endpoints/public/public.controller.ts @@ -46,7 +46,10 @@ export class PublicController { public async getPublicPortfolio( @Param('accessId') accessId: string ): Promise { - const access = await this.accessService.access({ id: accessId }); + const access = await this.accessService.access({ + granteeUserId: null, + id: accessId + }); if (!access) { throw new HttpException( 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 cca1eda03..c99fc40ac 100644 --- a/apps/client/src/app/pages/accounts/accounts-page.component.ts +++ b/apps/client/src/app/pages/accounts/accounts-page.component.ts @@ -17,7 +17,9 @@ import { DataService } from '@ghostfolio/ui/services'; import { ChangeDetectorRef, Component, + computed, DestroyRef, + inject, OnInit } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; @@ -25,7 +27,7 @@ import { MatDialog } from '@angular/material/dialog'; import { ActivatedRoute, Router, RouterModule } from '@angular/router'; import { Account as AccountModel } from '@prisma/client'; import { DeviceDetectorService } from 'ngx-device-detector'; -import { EMPTY, Subscription } from 'rxjs'; +import { EMPTY } from 'rxjs'; import { catchError } from 'rxjs/operators'; import { GfCreateOrUpdateAccountDialogComponent } from './create-or-update-account-dialog/create-or-update-account-dialog.component'; @@ -41,29 +43,33 @@ import { GfTransferBalanceDialogComponent } from './transfer-balance/transfer-ba templateUrl: './accounts-page.html' }) export class GfAccountsPageComponent implements OnInit { - public accounts: AccountModel[]; - public activitiesCount = 0; - public deviceType: string; - public hasImpersonationId: boolean; - public hasPermissionToCreateAccount: boolean; - public hasPermissionToUpdateAccount: boolean; - public routeQueryParams: Subscription; - public totalBalanceInBaseCurrency = 0; - public totalValueInBaseCurrency = 0; - public user: User; - - public constructor( - private changeDetectorRef: ChangeDetectorRef, - private dataService: DataService, - private destroyRef: DestroyRef, - private deviceDetectorService: DeviceDetectorService, - private dialog: MatDialog, - private impersonationStorageService: ImpersonationStorageService, - private notificationService: NotificationService, - private route: ActivatedRoute, - private router: Router, - private userService: UserService - ) { + protected accounts: AccountModel[]; + protected activitiesCount = 0; + protected hasImpersonationId: boolean; + protected hasPermissionToCreateAccount: boolean; + protected hasPermissionToUpdateAccount: boolean; + protected totalBalanceInBaseCurrency = 0; + protected totalValueInBaseCurrency = 0; + protected user: User; + + private readonly deviceType = computed( + () => this.deviceDetectorService.deviceInfo().deviceType + ); + + private readonly changeDetectorRef = inject(ChangeDetectorRef); + private readonly dataService = inject(DataService); + private readonly destroyRef = inject(DestroyRef); + private readonly deviceDetectorService = inject(DeviceDetectorService); + private readonly dialog = inject(MatDialog); + private readonly impersonationStorageService = inject( + ImpersonationStorageService + ); + private readonly notificationService = inject(NotificationService); + private readonly route = inject(ActivatedRoute); + private readonly router = inject(Router); + private readonly userService = inject(UserService); + + public constructor() { this.route.queryParams .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((params) => { @@ -80,7 +86,9 @@ export class GfAccountsPageComponent implements OnInit { return id === params['accountId']; }); - this.openUpdateAccountDialog(account); + if (account) { + this.openUpdateAccountDialog(account); + } } else { this.router.navigate(['.'], { relativeTo: this.route }); } @@ -91,8 +99,6 @@ export class GfAccountsPageComponent implements OnInit { } public ngOnInit() { - this.deviceType = this.deviceDetectorService.getDeviceInfo().deviceType; - this.impersonationStorageService .onChangeHasImpersonation() .pipe(takeUntilDestroyed(this.destroyRef)) @@ -122,32 +128,7 @@ export class GfAccountsPageComponent implements OnInit { this.fetchAccounts(); } - public fetchAccounts() { - this.dataService - .fetchAccounts() - .pipe(takeUntilDestroyed(this.destroyRef)) - .subscribe( - ({ - accounts, - activitiesCount, - totalBalanceInBaseCurrency, - totalValueInBaseCurrency - }) => { - this.accounts = accounts; - this.activitiesCount = activitiesCount; - this.totalBalanceInBaseCurrency = totalBalanceInBaseCurrency; - this.totalValueInBaseCurrency = totalValueInBaseCurrency; - - if (this.accounts?.length <= 0) { - this.router.navigate([], { queryParams: { createDialog: true } }); - } - - this.changeDetectorRef.markForCheck(); - } - ); - } - - public onDeleteAccount(aId: string) { + protected onDeleteAccount(aId: string) { this.reset(); this.dataService @@ -163,19 +144,44 @@ export class GfAccountsPageComponent implements OnInit { }); } - public onTransferBalance() { + protected onTransferBalance() { this.router.navigate([], { queryParams: { transferBalanceDialog: true } }); } - public onUpdateAccount(aAccount: AccountModel) { + protected onUpdateAccount(aAccount: AccountModel) { this.router.navigate([], { queryParams: { accountId: aAccount.id, editDialog: true } }); } - public openUpdateAccountDialog({ + private fetchAccounts() { + this.dataService + .fetchAccounts() + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe( + ({ + accounts, + activitiesCount, + totalBalanceInBaseCurrency, + totalValueInBaseCurrency + }) => { + this.accounts = accounts; + this.activitiesCount = activitiesCount; + this.totalBalanceInBaseCurrency = totalBalanceInBaseCurrency; + this.totalValueInBaseCurrency = totalValueInBaseCurrency; + + if (this.accounts?.length <= 0) { + this.router.navigate([], { queryParams: { createDialog: true } }); + } + + this.changeDetectorRef.markForCheck(); + } + ); + } + + private openUpdateAccountDialog({ balance, comment, currency, @@ -199,8 +205,8 @@ export class GfAccountsPageComponent implements OnInit { platformId } }, - height: this.deviceType === 'mobile' ? '98vh' : '80vh', - width: this.deviceType === 'mobile' ? '100vw' : '50rem' + height: this.deviceType() === 'mobile' ? '98vh' : '80vh', + width: this.deviceType() === 'mobile' ? '100vw' : '50rem' }); dialogRef @@ -237,15 +243,15 @@ export class GfAccountsPageComponent implements OnInit { autoFocus: false, data: { accountId: aAccountId, - deviceType: this.deviceType, + deviceType: this.deviceType(), hasImpersonationId: this.hasImpersonationId, hasPermissionToCreateActivity: !this.hasImpersonationId && hasPermission(this.user?.permissions, permissions.createActivity) && !this.user?.settings?.isRestrictedView }, - height: this.deviceType === 'mobile' ? '98vh' : '80vh', - width: this.deviceType === 'mobile' ? '100vw' : '50rem' + height: this.deviceType() === 'mobile' ? '98vh' : '80vh', + width: this.deviceType() === 'mobile' ? '100vw' : '50rem' }); dialogRef @@ -267,15 +273,15 @@ export class GfAccountsPageComponent implements OnInit { account: { balance: 0, comment: null, - currency: this.user?.settings?.baseCurrency, + currency: this.user?.settings?.baseCurrency ?? null, id: null, isExcluded: false, name: null, platformId: null } - }, - height: this.deviceType === 'mobile' ? '98vh' : '80vh', - width: this.deviceType === 'mobile' ? '100vw' : '50rem' + } satisfies CreateOrUpdateAccountDialogParams, + height: this.deviceType() === 'mobile' ? '98vh' : '80vh', + width: this.deviceType() === 'mobile' ? '100vw' : '50rem' }); dialogRef @@ -312,7 +318,7 @@ export class GfAccountsPageComponent implements OnInit { data: { accounts: this.accounts }, - width: this.deviceType === 'mobile' ? '100vw' : '50rem' + width: this.deviceType() === 'mobile' ? '100vw' : '50rem' }); dialogRef @@ -353,7 +359,7 @@ export class GfAccountsPageComponent implements OnInit { } private reset() { - this.accounts = undefined; + this.accounts = []; this.activitiesCount = 0; this.totalBalanceInBaseCurrency = 0; this.totalValueInBaseCurrency = 0; diff --git a/apps/client/src/app/pages/accounts/create-or-update-account-dialog/interfaces/interfaces.ts b/apps/client/src/app/pages/accounts/create-or-update-account-dialog/interfaces/interfaces.ts index a3e6272f8..469ebc844 100644 --- a/apps/client/src/app/pages/accounts/create-or-update-account-dialog/interfaces/interfaces.ts +++ b/apps/client/src/app/pages/accounts/create-or-update-account-dialog/interfaces/interfaces.ts @@ -1,5 +1,7 @@ import { Account } from '@prisma/client'; export interface CreateOrUpdateAccountDialogParams { - account: Omit; + account: Omit & { + id: string | null; + }; } diff --git a/package-lock.json b/package-lock.json index 8370a5765..ac172b504 100644 --- a/package-lock.json +++ b/package-lock.json @@ -95,7 +95,7 @@ "tablemark": "4.1.0", "twitter-api-v2": "1.29.0", "undici": "8.5.0", - "yahoo-finance2": "3.15.3", + "yahoo-finance2": "3.15.4", "zod": "4.4.3", "zone.js": "0.16.1" }, @@ -35734,9 +35734,9 @@ } }, "node_modules/yahoo-finance2": { - "version": "3.15.3", - "resolved": "https://registry.npmjs.org/yahoo-finance2/-/yahoo-finance2-3.15.3.tgz", - "integrity": "sha512-AFmVZ4ACg3QFT2a/hyJv4Scp2J45gm/6xetVgvUvXzZypqsqbgreXJad4i+qm0onj6yeCf2fPhvow7Eh0CwwzA==", + "version": "3.15.4", + "resolved": "https://registry.npmjs.org/yahoo-finance2/-/yahoo-finance2-3.15.4.tgz", + "integrity": "sha512-90eOw76iqS//ksQGL4d/VcchbysnpWzFXVuiBtG7uuImlBDdxBA0BtccxCuTvVZDDu1aXm1TqBGc+MPr6wNkyQ==", "license": "MIT", "dependencies": { "@deno/shim-deno": "~0.18.0", diff --git a/package.json b/package.json index 4585d34ca..b9d5be234 100644 --- a/package.json +++ b/package.json @@ -139,7 +139,7 @@ "tablemark": "4.1.0", "twitter-api-v2": "1.29.0", "undici": "8.5.0", - "yahoo-finance2": "3.15.3", + "yahoo-finance2": "3.15.4", "zod": "4.4.3", "zone.js": "0.16.1" },