Browse Source

Merge branch 'main' into translate-ukrainian

pull/7181/head
Thomas Kaul 2 weeks ago
committed by GitHub
parent
commit
301db0e69a
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      CHANGELOG.md
  2. 5
      apps/api/src/app/endpoints/public/public.controller.ts
  3. 140
      apps/client/src/app/pages/accounts/accounts-page.component.ts
  4. 4
      apps/client/src/app/pages/accounts/create-or-update-account-dialog/interfaces/interfaces.ts
  5. 8
      package-lock.json
  6. 2
      package.json

2
CHANGELOG.md

@ -9,8 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### 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 by translating various tooltips across the application
- Improved the language localization for Ukrainian (`uk`) - 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 ## 3.19.1 - 2026-07-03

5
apps/api/src/app/endpoints/public/public.controller.ts

@ -46,7 +46,10 @@ export class PublicController {
public async getPublicPortfolio( public async getPublicPortfolio(
@Param('accessId') accessId: string @Param('accessId') accessId: string
): Promise<PublicPortfolioResponse> { ): Promise<PublicPortfolioResponse> {
const access = await this.accessService.access({ id: accessId }); const access = await this.accessService.access({
granteeUserId: null,
id: accessId
});
if (!access) { if (!access) {
throw new HttpException( throw new HttpException(

140
apps/client/src/app/pages/accounts/accounts-page.component.ts

@ -17,7 +17,9 @@ import { DataService } from '@ghostfolio/ui/services';
import { import {
ChangeDetectorRef, ChangeDetectorRef,
Component, Component,
computed,
DestroyRef, DestroyRef,
inject,
OnInit OnInit
} from '@angular/core'; } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; 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 { ActivatedRoute, Router, RouterModule } from '@angular/router';
import { Account as AccountModel } from '@prisma/client'; import { Account as AccountModel } from '@prisma/client';
import { DeviceDetectorService } from 'ngx-device-detector'; import { DeviceDetectorService } from 'ngx-device-detector';
import { EMPTY, Subscription } from 'rxjs'; import { EMPTY } from 'rxjs';
import { catchError } from 'rxjs/operators'; import { catchError } from 'rxjs/operators';
import { GfCreateOrUpdateAccountDialogComponent } from './create-or-update-account-dialog/create-or-update-account-dialog.component'; 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' templateUrl: './accounts-page.html'
}) })
export class GfAccountsPageComponent implements OnInit { export class GfAccountsPageComponent implements OnInit {
public accounts: AccountModel[]; protected accounts: AccountModel[];
public activitiesCount = 0; protected activitiesCount = 0;
public deviceType: string; protected hasImpersonationId: boolean;
public hasImpersonationId: boolean; protected hasPermissionToCreateAccount: boolean;
public hasPermissionToCreateAccount: boolean; protected hasPermissionToUpdateAccount: boolean;
public hasPermissionToUpdateAccount: boolean; protected totalBalanceInBaseCurrency = 0;
public routeQueryParams: Subscription; protected totalValueInBaseCurrency = 0;
public totalBalanceInBaseCurrency = 0; protected user: User;
public totalValueInBaseCurrency = 0;
public user: User; private readonly deviceType = computed(
() => this.deviceDetectorService.deviceInfo().deviceType
public constructor( );
private changeDetectorRef: ChangeDetectorRef,
private dataService: DataService, private readonly changeDetectorRef = inject(ChangeDetectorRef);
private destroyRef: DestroyRef, private readonly dataService = inject(DataService);
private deviceDetectorService: DeviceDetectorService, private readonly destroyRef = inject(DestroyRef);
private dialog: MatDialog, private readonly deviceDetectorService = inject(DeviceDetectorService);
private impersonationStorageService: ImpersonationStorageService, private readonly dialog = inject(MatDialog);
private notificationService: NotificationService, private readonly impersonationStorageService = inject(
private route: ActivatedRoute, ImpersonationStorageService
private router: Router, );
private userService: UserService 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 this.route.queryParams
.pipe(takeUntilDestroyed(this.destroyRef)) .pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((params) => { .subscribe((params) => {
@ -80,7 +86,9 @@ export class GfAccountsPageComponent implements OnInit {
return id === params['accountId']; return id === params['accountId'];
}); });
this.openUpdateAccountDialog(account); if (account) {
this.openUpdateAccountDialog(account);
}
} else { } else {
this.router.navigate(['.'], { relativeTo: this.route }); this.router.navigate(['.'], { relativeTo: this.route });
} }
@ -91,8 +99,6 @@ export class GfAccountsPageComponent implements OnInit {
} }
public ngOnInit() { public ngOnInit() {
this.deviceType = this.deviceDetectorService.getDeviceInfo().deviceType;
this.impersonationStorageService this.impersonationStorageService
.onChangeHasImpersonation() .onChangeHasImpersonation()
.pipe(takeUntilDestroyed(this.destroyRef)) .pipe(takeUntilDestroyed(this.destroyRef))
@ -122,32 +128,7 @@ export class GfAccountsPageComponent implements OnInit {
this.fetchAccounts(); this.fetchAccounts();
} }
public fetchAccounts() { protected onDeleteAccount(aId: string) {
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) {
this.reset(); this.reset();
this.dataService this.dataService
@ -163,19 +144,44 @@ export class GfAccountsPageComponent implements OnInit {
}); });
} }
public onTransferBalance() { protected onTransferBalance() {
this.router.navigate([], { this.router.navigate([], {
queryParams: { transferBalanceDialog: true } queryParams: { transferBalanceDialog: true }
}); });
} }
public onUpdateAccount(aAccount: AccountModel) { protected onUpdateAccount(aAccount: AccountModel) {
this.router.navigate([], { this.router.navigate([], {
queryParams: { accountId: aAccount.id, editDialog: true } 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, balance,
comment, comment,
currency, currency,
@ -199,8 +205,8 @@ export class GfAccountsPageComponent implements OnInit {
platformId platformId
} }
}, },
height: this.deviceType === 'mobile' ? '98vh' : '80vh', height: this.deviceType() === 'mobile' ? '98vh' : '80vh',
width: this.deviceType === 'mobile' ? '100vw' : '50rem' width: this.deviceType() === 'mobile' ? '100vw' : '50rem'
}); });
dialogRef dialogRef
@ -237,15 +243,15 @@ export class GfAccountsPageComponent implements OnInit {
autoFocus: false, autoFocus: false,
data: { data: {
accountId: aAccountId, accountId: aAccountId,
deviceType: this.deviceType, deviceType: this.deviceType(),
hasImpersonationId: this.hasImpersonationId, hasImpersonationId: this.hasImpersonationId,
hasPermissionToCreateActivity: hasPermissionToCreateActivity:
!this.hasImpersonationId && !this.hasImpersonationId &&
hasPermission(this.user?.permissions, permissions.createActivity) && hasPermission(this.user?.permissions, permissions.createActivity) &&
!this.user?.settings?.isRestrictedView !this.user?.settings?.isRestrictedView
}, },
height: this.deviceType === 'mobile' ? '98vh' : '80vh', height: this.deviceType() === 'mobile' ? '98vh' : '80vh',
width: this.deviceType === 'mobile' ? '100vw' : '50rem' width: this.deviceType() === 'mobile' ? '100vw' : '50rem'
}); });
dialogRef dialogRef
@ -267,15 +273,15 @@ export class GfAccountsPageComponent implements OnInit {
account: { account: {
balance: 0, balance: 0,
comment: null, comment: null,
currency: this.user?.settings?.baseCurrency, currency: this.user?.settings?.baseCurrency ?? null,
id: null, id: null,
isExcluded: false, isExcluded: false,
name: null, name: null,
platformId: null platformId: null
} }
}, } satisfies CreateOrUpdateAccountDialogParams,
height: this.deviceType === 'mobile' ? '98vh' : '80vh', height: this.deviceType() === 'mobile' ? '98vh' : '80vh',
width: this.deviceType === 'mobile' ? '100vw' : '50rem' width: this.deviceType() === 'mobile' ? '100vw' : '50rem'
}); });
dialogRef dialogRef
@ -312,7 +318,7 @@ export class GfAccountsPageComponent implements OnInit {
data: { data: {
accounts: this.accounts accounts: this.accounts
}, },
width: this.deviceType === 'mobile' ? '100vw' : '50rem' width: this.deviceType() === 'mobile' ? '100vw' : '50rem'
}); });
dialogRef dialogRef
@ -353,7 +359,7 @@ export class GfAccountsPageComponent implements OnInit {
} }
private reset() { private reset() {
this.accounts = undefined; this.accounts = [];
this.activitiesCount = 0; this.activitiesCount = 0;
this.totalBalanceInBaseCurrency = 0; this.totalBalanceInBaseCurrency = 0;
this.totalValueInBaseCurrency = 0; this.totalValueInBaseCurrency = 0;

4
apps/client/src/app/pages/accounts/create-or-update-account-dialog/interfaces/interfaces.ts

@ -1,5 +1,7 @@
import { Account } from '@prisma/client'; import { Account } from '@prisma/client';
export interface CreateOrUpdateAccountDialogParams { export interface CreateOrUpdateAccountDialogParams {
account: Omit<Account, 'createdAt' | 'updatedAt' | 'userId'>; account: Omit<Account, 'createdAt' | 'id' | 'updatedAt' | 'userId'> & {
id: string | null;
};
} }

8
package-lock.json

@ -95,7 +95,7 @@
"tablemark": "4.1.0", "tablemark": "4.1.0",
"twitter-api-v2": "1.29.0", "twitter-api-v2": "1.29.0",
"undici": "8.5.0", "undici": "8.5.0",
"yahoo-finance2": "3.15.3", "yahoo-finance2": "3.15.4",
"zod": "4.4.3", "zod": "4.4.3",
"zone.js": "0.16.1" "zone.js": "0.16.1"
}, },
@ -35734,9 +35734,9 @@
} }
}, },
"node_modules/yahoo-finance2": { "node_modules/yahoo-finance2": {
"version": "3.15.3", "version": "3.15.4",
"resolved": "https://registry.npmjs.org/yahoo-finance2/-/yahoo-finance2-3.15.3.tgz", "resolved": "https://registry.npmjs.org/yahoo-finance2/-/yahoo-finance2-3.15.4.tgz",
"integrity": "sha512-AFmVZ4ACg3QFT2a/hyJv4Scp2J45gm/6xetVgvUvXzZypqsqbgreXJad4i+qm0onj6yeCf2fPhvow7Eh0CwwzA==", "integrity": "sha512-90eOw76iqS//ksQGL4d/VcchbysnpWzFXVuiBtG7uuImlBDdxBA0BtccxCuTvVZDDu1aXm1TqBGc+MPr6wNkyQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@deno/shim-deno": "~0.18.0", "@deno/shim-deno": "~0.18.0",

2
package.json

@ -139,7 +139,7 @@
"tablemark": "4.1.0", "tablemark": "4.1.0",
"twitter-api-v2": "1.29.0", "twitter-api-v2": "1.29.0",
"undici": "8.5.0", "undici": "8.5.0",
"yahoo-finance2": "3.15.3", "yahoo-finance2": "3.15.4",
"zod": "4.4.3", "zod": "4.4.3",
"zone.js": "0.16.1" "zone.js": "0.16.1"
}, },

Loading…
Cancel
Save