Browse Source

Merge branch 'main' into feature/setup-statistics-gathering-queue

pull/6696/head
Thomas Kaul 2 months ago
committed by GitHub
parent
commit
25f6dc90b8
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 11
      CHANGELOG.md
  2. 6
      apps/api/src/app/admin/admin.service.ts
  3. 10
      apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts
  4. 46
      apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html
  5. 8
      apps/client/src/app/components/admin-overview/admin-overview.html
  6. 2
      apps/client/src/app/components/user-account-access/user-account-access.component.ts
  7. 11
      apps/client/src/app/services/user/user.service.ts
  8. 298
      apps/client/src/locales/messages.ca.xlf
  9. 298
      apps/client/src/locales/messages.de.xlf
  10. 298
      apps/client/src/locales/messages.es.xlf
  11. 298
      apps/client/src/locales/messages.fr.xlf
  12. 298
      apps/client/src/locales/messages.it.xlf
  13. 298
      apps/client/src/locales/messages.ko.xlf
  14. 298
      apps/client/src/locales/messages.nl.xlf
  15. 298
      apps/client/src/locales/messages.pl.xlf
  16. 298
      apps/client/src/locales/messages.pt.xlf
  17. 298
      apps/client/src/locales/messages.tr.xlf
  18. 298
      apps/client/src/locales/messages.uk.xlf
  19. 297
      apps/client/src/locales/messages.xlf
  20. 298
      apps/client/src/locales/messages.zh.xlf
  21. 2
      libs/common/src/lib/config.ts
  22. 13
      libs/common/src/lib/utils/form.util.ts
  23. 4
      libs/common/src/lib/utils/index.ts
  24. 4
      libs/ui/src/lib/activity-type/activity-type.component.html
  25. 1
      libs/ui/src/lib/i18n.ts
  26. 22
      package-lock.json
  27. 4
      package.json
  28. 2
      prisma/migrations/20260409154017_added_loan_to_asset_sub_class/migration.sql
  29. 1
      prisma/schema.prisma

11
CHANGELOG.md

@ -7,9 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
### Added
- Added loan as an asset sub class
### Changed
- Extended the asset profile details dialog in the admin control panel to support editing countries for all asset types
- Extended the asset profile details dialog in the admin control panel to support editing sectors for all asset types
- Migrated the data collection for the _Open Startup_ (`/open`) page to the queue design pattern
- Upgraded `lodash` from version `4.17.23` to `4.18.1`
### Fixed
- Improved the style of the activity type component
## 2.253.0 - 2026-03-06

6
apps/api/src/app/admin/admin.service.ts

@ -625,23 +625,23 @@ export class AdminService {
const symbolProfileOverrides = {
assetClass: assetClass as AssetClass,
assetSubClass: assetSubClass as AssetSubClass,
countries: countries as Prisma.JsonArray,
name: name as string,
sectors: sectors as Prisma.JsonArray,
url: url as string
};
const updatedSymbolProfile: Prisma.SymbolProfileUpdateInput = {
comment,
countries,
currency,
dataSource,
holdings,
isActive,
scraperConfiguration,
sectors,
symbol,
symbolMapping,
...(dataSource === 'MANUAL'
? { assetClass, assetSubClass, name, url }
? { assetClass, assetSubClass, countries, name, sectors, url }
: {
SymbolProfileOverrides: {
upsert: {

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

@ -19,7 +19,7 @@ import {
User
} from '@ghostfolio/common/interfaces';
import { DateRange } from '@ghostfolio/common/types';
import { validateObjectForForm } from '@ghostfolio/common/utils';
import { jsonValidator, validateObjectForForm } from '@ghostfolio/common/utils';
import { GfCurrencySelectorComponent } from '@ghostfolio/ui/currency-selector';
import { GfEntityLogoComponent } from '@ghostfolio/ui/entity-logo';
import { GfHistoricalMarketDataEditorComponent } from '@ghostfolio/ui/historical-market-data-editor';
@ -149,7 +149,7 @@ export class GfAssetProfileDialogComponent implements OnInit {
assetClass: new FormControl<AssetClass>(undefined),
assetSubClass: new FormControl<AssetSubClass>(undefined),
comment: '',
countries: '',
countries: ['', jsonValidator()],
currency: '',
historicalData: this.formBuilder.group({
csvString: ''
@ -158,14 +158,14 @@ export class GfAssetProfileDialogComponent implements OnInit {
name: ['', Validators.required],
scraperConfiguration: this.formBuilder.group({
defaultMarketPrice: null,
headers: JSON.stringify({}),
headers: [JSON.stringify({}), jsonValidator()],
locale: '',
mode: '',
selector: '',
url: ''
}),
sectors: '',
symbolMapping: '',
sectors: ['', jsonValidator()],
symbolMapping: ['', jsonValidator()],
url: ''
});

46
apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html

@ -393,30 +393,28 @@
></textarea>
</mat-form-field>
</div>
@if (assetProfile?.dataSource === 'MANUAL') {
<div>
<mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Sectors</mat-label>
<textarea
cdkTextareaAutosize
formControlName="sectors"
matInput
type="text"
></textarea>
</mat-form-field>
</div>
<div>
<mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Countries</mat-label>
<textarea
cdkTextareaAutosize
formControlName="countries"
matInput
type="text"
></textarea>
</mat-form-field>
</div>
}
<div>
<mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Sectors</mat-label>
<textarea
cdkTextareaAutosize
formControlName="sectors"
matInput
type="text"
></textarea>
</mat-form-field>
</div>
<div>
<mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Countries</mat-label>
<textarea
cdkTextareaAutosize
formControlName="countries"
matInput
type="text"
></textarea>
</mat-form-field>
</div>
<div>
<mat-form-field appearance="outline" class="w-100 without-hint">
<mat-label i18n>Url</mat-label>

8
apps/client/src/app/components/admin-overview/admin-overview.html

@ -107,7 +107,13 @@
<table>
@for (coupon of coupons; track coupon) {
<tr>
<td class="text-monospace">{{ coupon.code }}</td>
<td>
<gf-value
class="text-monospace"
[enableCopyToClipboardButton]="true"
[value]="coupon.code"
/>
</td>
<td class="pl-2 text-right">
{{ formatStringValue(coupon.duration) }}
</td>

2
apps/client/src/app/components/user-account-access/user-account-access.component.ts

@ -212,8 +212,6 @@ export class GfUserAccountAccessComponent implements OnInit {
});
if (!access) {
console.log('Could not find access.');
return;
}

11
apps/client/src/app/services/user/user.service.ts

@ -3,14 +3,15 @@ import { Filter, User } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { DestroyRef, Injectable } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { MatDialog } from '@angular/material/dialog';
import { ObservableStore } from '@codewithdan/observable-store';
import { parseISO } from 'date-fns';
import { DeviceDetectorService } from 'ngx-device-detector';
import { Observable, Subject, of } from 'rxjs';
import { Observable, of } from 'rxjs';
import { throwError } from 'rxjs';
import { catchError, map, takeUntil } from 'rxjs/operators';
import { catchError, map } from 'rxjs/operators';
import { SubscriptionInterstitialDialogParams } from '../../components/subscription-interstitial-dialog/interfaces/interfaces';
import { GfSubscriptionInterstitialDialogComponent } from '../../components/subscription-interstitial-dialog/subscription-interstitial-dialog.component';
@ -22,9 +23,9 @@ import { UserStoreState } from './user-store.state';
})
export class UserService extends ObservableStore<UserStoreState> {
private deviceType: string;
private unsubscribeSubject = new Subject<void>();
public constructor(
private destroyRef: DestroyRef,
private deviceService: DeviceDetectorService,
private dialog: MatDialog,
private http: HttpClient,
@ -163,7 +164,7 @@ export class UserService extends ObservableStore<UserStoreState> {
dialogRef
.afterClosed()
.pipe(takeUntil(this.unsubscribeSubject))
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe();
}

298
apps/client/src/locales/messages.ca.xlf

File diff suppressed because it is too large

298
apps/client/src/locales/messages.de.xlf

File diff suppressed because it is too large

298
apps/client/src/locales/messages.es.xlf

File diff suppressed because it is too large

298
apps/client/src/locales/messages.fr.xlf

File diff suppressed because it is too large

298
apps/client/src/locales/messages.it.xlf

File diff suppressed because it is too large

298
apps/client/src/locales/messages.ko.xlf

File diff suppressed because it is too large

298
apps/client/src/locales/messages.nl.xlf

File diff suppressed because it is too large

298
apps/client/src/locales/messages.pl.xlf

File diff suppressed because it is too large

298
apps/client/src/locales/messages.pt.xlf

File diff suppressed because it is too large

298
apps/client/src/locales/messages.tr.xlf

File diff suppressed because it is too large

298
apps/client/src/locales/messages.uk.xlf

File diff suppressed because it is too large

297
apps/client/src/locales/messages.xlf

File diff suppressed because it is too large

298
apps/client/src/locales/messages.zh.xlf

File diff suppressed because it is too large

2
libs/common/src/lib/config.ts

@ -46,7 +46,7 @@ export const ASSET_CLASS_MAPPING = new Map<AssetClass, AssetSubClass[]>([
AssetSubClass.STOCK
]
],
[AssetClass.FIXED_INCOME, [AssetSubClass.BOND]],
[AssetClass.FIXED_INCOME, [AssetSubClass.BOND, AssetSubClass.LOAN]],
[AssetClass.LIQUIDITY, [AssetSubClass.CRYPTOCURRENCY]],
[AssetClass.REAL_ESTATE, []]
]);

13
libs/common/src/lib/utils/form.util.ts

@ -1,6 +1,17 @@
import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';
import { FormGroup } from '@angular/forms';
import { plainToInstance } from 'class-transformer';
import { validate } from 'class-validator';
import { isJSON, validate } from 'class-validator';
export function jsonValidator(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
if (!isJSON(control.value)) {
return { invalidJson: true };
}
return null;
};
}
export async function validateObjectForForm<T>({
classDto,

4
libs/common/src/lib/utils/index.ts

@ -1,3 +1,3 @@
import { validateObjectForForm } from './form.util';
import { jsonValidator, validateObjectForForm } from './form.util';
export { validateObjectForForm };
export { jsonValidator, validateObjectForForm };

4
libs/ui/src/lib/activity-type/activity-type.component.html

@ -20,5 +20,7 @@
} @else if (activityType === 'SELL') {
<ion-icon name="arrow-down-circle-outline" />
}
<span class="d-none d-lg-block mx-1">{{ activityTypeLabel }}</span>
<span class="d-none d-lg-block mx-1 text-nowrap">
{{ activityTypeLabel }}
</span>
</div>

1
libs/ui/src/lib/i18n.ts

@ -56,6 +56,7 @@ const locales = {
COLLECTIBLE: $localize`Collectible`,
CRYPTOCURRENCY: $localize`Cryptocurrency`,
ETF: $localize`ETF`,
LOAN: $localize`Loan`,
MUTUALFUND: $localize`Mutual Fund`,
PRECIOUS_METAL: $localize`Precious Metal`,
PRIVATE_EQUITY: $localize`Private Equity`,

22
package-lock.json

@ -73,7 +73,7 @@
"http-status-codes": "2.3.0",
"ionicons": "8.0.13",
"jsonpath": "1.2.1",
"lodash": "4.17.23",
"lodash": "4.18.1",
"marked": "17.0.2",
"ms": "3.0.0-canary.1",
"ng-extract-i18n-merge": "3.3.0",
@ -132,7 +132,7 @@
"@types/google-spreadsheet": "3.1.5",
"@types/jest": "30.0.0",
"@types/jsonpath": "0.2.4",
"@types/lodash": "4.17.23",
"@types/lodash": "4.17.24",
"@types/node": "22.15.17",
"@types/papaparse": "5.3.7",
"@types/passport-google-oauth20": "2.0.17",
@ -7654,6 +7654,12 @@
"rxjs": "^7.1.0"
}
},
"node_modules/@nestjs/config/node_modules/lodash": {
"version": "4.17.23",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
"license": "MIT"
},
"node_modules/@nestjs/core": {
"version": "11.1.14",
"resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.14.tgz",
@ -13312,9 +13318,9 @@
}
},
"node_modules/@types/lodash": {
"version": "4.17.23",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.23.tgz",
"integrity": "sha512-RDvF6wTulMPjrNdCoYRC8gNR880JNGT8uB+REUpC2Ns4pRqQJhGz90wh7rgdXDPpCczF3VGktDuFGVnz8zP7HA==",
"version": "4.17.24",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.24.tgz",
"integrity": "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==",
"dev": true,
"license": "MIT"
},
@ -25317,9 +25323,9 @@
}
},
"node_modules/lodash": {
"version": "4.17.23",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
"integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
"license": "MIT"
},
"node_modules/lodash-es": {

4
package.json

@ -118,7 +118,7 @@
"http-status-codes": "2.3.0",
"ionicons": "8.0.13",
"jsonpath": "1.2.1",
"lodash": "4.17.23",
"lodash": "4.18.1",
"marked": "17.0.2",
"ms": "3.0.0-canary.1",
"ng-extract-i18n-merge": "3.3.0",
@ -177,7 +177,7 @@
"@types/google-spreadsheet": "3.1.5",
"@types/jest": "30.0.0",
"@types/jsonpath": "0.2.4",
"@types/lodash": "4.17.23",
"@types/lodash": "4.17.24",
"@types/node": "22.15.17",
"@types/papaparse": "5.3.7",
"@types/passport-google-oauth20": "2.0.17",

2
prisma/migrations/20260409154017_added_loan_to_asset_sub_class/migration.sql

@ -0,0 +1,2 @@
-- AlterEnum
ALTER TYPE "AssetSubClass" ADD VALUE 'LOAN';

1
prisma/schema.prisma

@ -309,6 +309,7 @@ enum AssetSubClass {
COMMODITY
CRYPTOCURRENCY
ETF
LOAN
MUTUALFUND
PRECIOUS_METAL
PRIVATE_EQUITY

Loading…
Cancel
Save