Browse Source

Merge branch 'ghostfolio:main' into task/asset-profile-dialog-type-safety

pull/6722/head
Kenrick Tandrian 1 month ago
committed by GitHub
parent
commit
631fc3ac7d
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      .gitignore
  2. 7
      CHANGELOG.md
  3. 2
      apps/api/src/environments/environment.prod.ts
  4. 6
      apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts
  5. 8
      apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html
  6. 88
      apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.component.ts
  7. 10
      apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/interfaces/interfaces.ts
  8. 2
      libs/ui/src/lib/holdings-table/holdings-table.component.ts
  9. 3
      nx.json
  10. 36218
      package-lock.json
  11. 70
      package.json

2
.gitignore

@ -25,6 +25,8 @@ npm-debug.log
# misc # misc
/.angular/cache /.angular/cache
.claude/settings.local.json
.claude/worktrees
.cursor/rules/nx-rules.mdc .cursor/rules/nx-rules.mdc
.env .env
.env.prod .env.prod

7
CHANGELOG.md

@ -5,6 +5,13 @@ 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/), 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). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
### Changed
- Upgraded `angular` from version `21.1.1` to `21.2.7`
- Upgraded `Nx` from version `22.5.3` to `22.6.4`
## 2.254.0 - 2026-03-10 ## 2.254.0 - 2026-03-10
### Added ### Added

2
apps/api/src/environments/environment.prod.ts

@ -3,5 +3,5 @@ import { DEFAULT_HOST, DEFAULT_PORT } from '@ghostfolio/common/config';
export const environment = { export const environment = {
production: true, production: true,
rootUrl: `http://${DEFAULT_HOST}:${DEFAULT_PORT}`, rootUrl: `http://${DEFAULT_HOST}:${DEFAULT_PORT}`,
version: `${require('../../../../package.json').version}` version: process.env.npm_package_version ?? 'dev'
}; };

6
apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts

@ -208,6 +208,12 @@ export class GfAccountDetailDialogComponent implements OnInit {
this.dialogRef.close(); this.dialogRef.close();
} }
public showValuesInPercentage() {
return (
this.data.hasImpersonationId || this.user?.settings?.isRestrictedView
);
}
private fetchAccount() { private fetchAccount() {
this.dataService this.dataService
.fetchAccount(this.data.accountId) .fetchAccount(this.data.accountId)

8
apps/client/src/app/components/account-detail-dialog/account-detail-dialog.html

@ -24,9 +24,7 @@
class="h-100" class="h-100"
[currency]="user?.settings?.baseCurrency" [currency]="user?.settings?.baseCurrency"
[historicalDataItems]="historicalDataItems" [historicalDataItems]="historicalDataItems"
[isInPercentage]=" [isInPercentage]="showValuesInPercentage()"
data.hasImpersonationId || user.settings.isRestrictedView
"
[isLoading]="isLoadingChart" [isLoading]="isLoadingChart"
[locale]="user?.settings?.locale" [locale]="user?.settings?.locale"
/> />
@ -118,9 +116,7 @@
[deviceType]="data.deviceType" [deviceType]="data.deviceType"
[hasPermissionToCreateActivity]="false" [hasPermissionToCreateActivity]="false"
[hasPermissionToDeleteActivity]="false" [hasPermissionToDeleteActivity]="false"
[hasPermissionToExportActivities]=" [hasPermissionToExportActivities]="!showValuesInPercentage()"
!data.hasImpersonationId && !user.settings.isRestrictedView
"
[hasPermissionToFilter]="false" [hasPermissionToFilter]="false"
[hasPermissionToOpenDetails]="false" [hasPermissionToOpenDetails]="false"
[locale]="user?.settings?.locale" [locale]="user?.settings?.locale"

88
apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/create-asset-profile-dialog.component.ts

@ -3,6 +3,7 @@ import {
ghostfolioPrefix, ghostfolioPrefix,
PROPERTY_CURRENCIES PROPERTY_CURRENCIES
} from '@ghostfolio/common/config'; } from '@ghostfolio/common/config';
import type { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';
import { AdminService, DataService } from '@ghostfolio/ui/services'; import { AdminService, DataService } from '@ghostfolio/ui/services';
import { GfSymbolAutocompleteComponent } from '@ghostfolio/ui/symbol-autocomplete'; import { GfSymbolAutocompleteComponent } from '@ghostfolio/ui/symbol-autocomplete';
@ -11,6 +12,7 @@ import {
ChangeDetectorRef, ChangeDetectorRef,
Component, Component,
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';
@ -18,7 +20,6 @@ import {
AbstractControl, AbstractControl,
FormBuilder, FormBuilder,
FormControl, FormControl,
FormGroup,
FormsModule, FormsModule,
ReactiveFormsModule, ReactiveFormsModule,
ValidationErrors, ValidationErrors,
@ -34,7 +35,10 @@ import { DataSource } from '@prisma/client';
import { isISO4217CurrencyCode } from 'class-validator'; import { isISO4217CurrencyCode } from 'class-validator';
import { switchMap } from 'rxjs'; import { switchMap } from 'rxjs';
import { CreateAssetProfileDialogMode } from './interfaces/interfaces'; import type {
CreateAssetProfileDialogMode,
CreateAssetProfileForm
} from './interfaces/interfaces';
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
@ -54,32 +58,44 @@ import { CreateAssetProfileDialogMode } from './interfaces/interfaces';
templateUrl: 'create-asset-profile-dialog.html' templateUrl: 'create-asset-profile-dialog.html'
}) })
export class GfCreateAssetProfileDialogComponent implements OnInit { export class GfCreateAssetProfileDialogComponent implements OnInit {
public createAssetProfileForm: FormGroup; protected createAssetProfileForm: CreateAssetProfileForm;
public ghostfolioPrefix = `${ghostfolioPrefix}_`; protected readonly ghostfolioPrefix = `${ghostfolioPrefix}_`;
public mode: CreateAssetProfileDialogMode; protected mode: CreateAssetProfileDialogMode;
private customCurrencies: string[]; private customCurrencies: string[];
private dataSourceForExchangeRates: DataSource; private dataSourceForExchangeRates: DataSource;
public constructor( private readonly adminService = inject(AdminService);
public readonly adminService: AdminService, private readonly changeDetectorRef = inject(ChangeDetectorRef);
private readonly changeDetectorRef: ChangeDetectorRef, private readonly dataService = inject(DataService);
private readonly dataService: DataService, private readonly destroyRef = inject(DestroyRef);
private readonly destroyRef: DestroyRef, private readonly dialogRef =
public readonly dialogRef: MatDialogRef<GfCreateAssetProfileDialogComponent>, inject<MatDialogRef<GfCreateAssetProfileDialogComponent>>(MatDialogRef);
public readonly formBuilder: FormBuilder private readonly formBuilder = inject(FormBuilder);
) {}
protected get showCurrencyErrorMessage() {
const addCurrencyFormControl =
this.createAssetProfileForm.controls.addCurrency;
if (addCurrencyFormControl.hasError('invalidCurrency')) {
return true;
}
return false;
}
public ngOnInit() { public ngOnInit() {
this.initialize(); this.initialize();
this.createAssetProfileForm = this.formBuilder.group( this.createAssetProfileForm = this.formBuilder.group(
{ {
addCurrency: new FormControl(null, [ addCurrency: new FormControl<string | null>(null, [
this.iso4217CurrencyCodeValidator() this.iso4217CurrencyCodeValidator()
]), ]),
addSymbol: new FormControl(null, [Validators.required]), addSymbol: new FormControl<string | null>(null, [Validators.required]),
searchSymbol: new FormControl(null, [Validators.required]) searchSymbol: new FormControl<AssetProfileIdentifier | null>(null, [
Validators.required
])
}, },
{ {
validators: this.atLeastOneValid validators: this.atLeastOneValid
@ -104,12 +120,11 @@ export class GfCreateAssetProfileDialogComponent implements OnInit {
this.dialogRef.close({ this.dialogRef.close({
addAssetProfile: true, addAssetProfile: true,
dataSource: dataSource:
this.createAssetProfileForm.get('searchSymbol').value.dataSource, this.createAssetProfileForm.controls.searchSymbol.value?.dataSource,
symbol: this.createAssetProfileForm.get('searchSymbol').value.symbol symbol: this.createAssetProfileForm.controls.searchSymbol.value?.symbol
}); });
} else if (this.mode === 'currency') { } else if (this.mode === 'currency') {
const currency = this.createAssetProfileForm.get('addCurrency') const currency = this.createAssetProfileForm.controls.addCurrency.value;
.value as string;
const currencies = Array.from( const currencies = Array.from(
new Set([...this.customCurrencies, currency]) new Set([...this.customCurrencies, currency])
@ -139,26 +154,15 @@ export class GfCreateAssetProfileDialogComponent implements OnInit {
this.dialogRef.close({ this.dialogRef.close({
addAssetProfile: true, addAssetProfile: true,
dataSource: 'MANUAL', dataSource: 'MANUAL',
symbol: `${this.ghostfolioPrefix}${this.createAssetProfileForm.get('addSymbol').value}` symbol: `${this.ghostfolioPrefix}${this.createAssetProfileForm.controls.addSymbol.value}`
}); });
} }
} }
public get showCurrencyErrorMessage() { private atLeastOneValid(control: CreateAssetProfileForm): ValidationErrors {
const addCurrencyFormControl = const addCurrencyControl = control.controls.addCurrency;
this.createAssetProfileForm.get('addCurrency'); const addSymbolControl = control.controls.addSymbol;
const searchSymbolControl = control.controls.searchSymbol;
if (addCurrencyFormControl.hasError('invalidCurrency')) {
return true;
}
return false;
}
private atLeastOneValid(control: AbstractControl): ValidationErrors {
const addCurrencyControl = control.get('addCurrency');
const addSymbolControl = control.get('addSymbol');
const searchSymbolControl = control.get('searchSymbol');
if ( if (
addCurrencyControl.valid && addCurrencyControl.valid &&
@ -170,11 +174,8 @@ export class GfCreateAssetProfileDialogComponent implements OnInit {
if ( if (
addCurrencyControl.valid || addCurrencyControl.valid ||
!addCurrencyControl ||
addSymbolControl.valid || addSymbolControl.valid ||
!addSymbolControl || searchSymbolControl.valid
searchSymbolControl.valid ||
!searchSymbolControl
) { ) {
return { atLeastOneValid: false }; return { atLeastOneValid: false };
} }
@ -189,11 +190,14 @@ export class GfCreateAssetProfileDialogComponent implements OnInit {
.subscribe(({ dataProviders, settings }) => { .subscribe(({ dataProviders, settings }) => {
this.customCurrencies = settings[PROPERTY_CURRENCIES] as string[]; this.customCurrencies = settings[PROPERTY_CURRENCIES] as string[];
const { dataSource } = dataProviders.find(({ useForExchangeRates }) => { const { dataSource } =
dataProviders.find(({ useForExchangeRates }) => {
return useForExchangeRates; return useForExchangeRates;
}); }) ?? {};
if (dataSource) {
this.dataSourceForExchangeRates = dataSource; this.dataSourceForExchangeRates = dataSource;
}
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();
}); });

10
apps/client/src/app/components/admin-market-data/create-asset-profile-dialog/interfaces/interfaces.ts

@ -1,6 +1,16 @@
import type { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';
import type { FormControl, FormGroup } from '@angular/forms';
export interface CreateAssetProfileDialogParams { export interface CreateAssetProfileDialogParams {
deviceType: string; deviceType: string;
locale: string; locale: string;
} }
export type CreateAssetProfileDialogMode = 'auto' | 'currency' | 'manual'; export type CreateAssetProfileDialogMode = 'auto' | 'currency' | 'manual';
export type CreateAssetProfileForm = FormGroup<{
addCurrency: FormControl<string | null>;
addSymbol: FormControl<string | null>;
searchSymbol: FormControl<AssetProfileIdentifier | null>;
}>;

2
libs/ui/src/lib/holdings-table/holdings-table.component.ts

@ -4,7 +4,6 @@ import {
PortfolioPosition PortfolioPosition
} from '@ghostfolio/common/interfaces'; } from '@ghostfolio/common/interfaces';
import { CommonModule } from '@angular/common';
import { import {
CUSTOM_ELEMENTS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA,
ChangeDetectionStrategy, ChangeDetectionStrategy,
@ -30,7 +29,6 @@ import { GfValueComponent } from '../value/value.component';
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [ imports: [
CommonModule,
GfEntityLogoComponent, GfEntityLogoComponent,
GfValueComponent, GfValueComponent,
MatButtonModule, MatButtonModule,

3
nx.json

@ -125,5 +125,6 @@
] ]
}, },
"parallel": 1, "parallel": 1,
"defaultBase": "origin/main" "defaultBase": "origin/main",
"analytics": false
} }

36218
package-lock.json

File diff suppressed because it is too large

70
package.json

@ -55,17 +55,17 @@
"workspace-generator": "nx workspace-generator" "workspace-generator": "nx workspace-generator"
}, },
"dependencies": { "dependencies": {
"@angular/animations": "21.1.1", "@angular/animations": "21.2.7",
"@angular/cdk": "21.1.1", "@angular/cdk": "21.2.5",
"@angular/common": "21.1.1", "@angular/common": "21.2.7",
"@angular/compiler": "21.1.1", "@angular/compiler": "21.2.7",
"@angular/core": "21.1.1", "@angular/core": "21.2.7",
"@angular/forms": "21.1.1", "@angular/forms": "21.2.7",
"@angular/material": "21.1.1", "@angular/material": "21.2.5",
"@angular/platform-browser": "21.1.1", "@angular/platform-browser": "21.2.7",
"@angular/platform-browser-dynamic": "21.1.1", "@angular/platform-browser-dynamic": "21.2.7",
"@angular/router": "21.1.1", "@angular/router": "21.2.7",
"@angular/service-worker": "21.1.1", "@angular/service-worker": "21.2.7",
"@bull-board/api": "6.20.3", "@bull-board/api": "6.20.3",
"@bull-board/express": "6.20.3", "@bull-board/express": "6.20.3",
"@bull-board/nestjs": "6.20.3", "@bull-board/nestjs": "6.20.3",
@ -139,35 +139,35 @@
"tablemark": "4.1.0", "tablemark": "4.1.0",
"twitter-api-v2": "1.29.0", "twitter-api-v2": "1.29.0",
"yahoo-finance2": "3.13.2", "yahoo-finance2": "3.13.2",
"zone.js": "0.16.0" "zone.js": "0.16.1"
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "21.1.1", "@angular-devkit/build-angular": "21.2.6",
"@angular-devkit/core": "21.1.1", "@angular-devkit/core": "21.2.6",
"@angular-devkit/schematics": "21.1.1", "@angular-devkit/schematics": "21.2.6",
"@angular-eslint/eslint-plugin": "21.1.0", "@angular-eslint/eslint-plugin": "21.2.0",
"@angular-eslint/eslint-plugin-template": "21.1.0", "@angular-eslint/eslint-plugin-template": "21.2.0",
"@angular-eslint/template-parser": "21.1.0", "@angular-eslint/template-parser": "21.2.0",
"@angular/cli": "21.1.1", "@angular/cli": "21.2.6",
"@angular/compiler-cli": "21.1.1", "@angular/compiler-cli": "21.2.7",
"@angular/language-service": "21.1.1", "@angular/language-service": "21.2.7",
"@angular/localize": "21.1.1", "@angular/localize": "21.2.7",
"@angular/pwa": "21.1.1", "@angular/pwa": "21.2.6",
"@eslint/eslintrc": "3.3.1", "@eslint/eslintrc": "3.3.1",
"@eslint/js": "9.35.0", "@eslint/js": "9.35.0",
"@nestjs/schematics": "11.0.9", "@nestjs/schematics": "11.0.9",
"@nestjs/testing": "11.1.14", "@nestjs/testing": "11.1.14",
"@nx/angular": "22.5.3", "@nx/angular": "22.6.4",
"@nx/eslint-plugin": "22.5.3", "@nx/eslint-plugin": "22.6.4",
"@nx/jest": "22.5.3", "@nx/jest": "22.6.4",
"@nx/js": "22.5.3", "@nx/js": "22.6.4",
"@nx/module-federation": "22.5.3", "@nx/module-federation": "22.6.4",
"@nx/nest": "22.5.3", "@nx/nest": "22.6.4",
"@nx/node": "22.5.3", "@nx/node": "22.6.4",
"@nx/storybook": "22.5.3", "@nx/storybook": "22.6.4",
"@nx/web": "22.5.3", "@nx/web": "22.6.4",
"@nx/workspace": "22.5.3", "@nx/workspace": "22.6.4",
"@schematics/angular": "21.1.1", "@schematics/angular": "21.2.6",
"@storybook/addon-docs": "10.1.10", "@storybook/addon-docs": "10.1.10",
"@storybook/angular": "10.1.10", "@storybook/angular": "10.1.10",
"@trivago/prettier-plugin-sort-imports": "6.0.2", "@trivago/prettier-plugin-sort-imports": "6.0.2",
@ -192,7 +192,7 @@
"jest": "30.2.0", "jest": "30.2.0",
"jest-environment-jsdom": "30.2.0", "jest-environment-jsdom": "30.2.0",
"jest-preset-angular": "16.0.0", "jest-preset-angular": "16.0.0",
"nx": "22.5.3", "nx": "22.6.4",
"prettier": "3.8.1", "prettier": "3.8.1",
"prettier-plugin-organize-attributes": "1.0.0", "prettier-plugin-organize-attributes": "1.0.0",
"prisma": "6.19.3", "prisma": "6.19.3",

Loading…
Cancel
Save