Browse Source

Merge remote-tracking branch 'origin/main' into task/upgrade-nx-22.3

pull/6109/head
Kenrick Tandrian 1 month ago
parent
commit
715a4c528d
  1. 7
      CHANGELOG.md
  2. 2
      apps/api/src/app/app.module.ts
  3. 22
      apps/api/src/app/endpoints/platforms/platforms.controller.ts
  4. 11
      apps/api/src/app/endpoints/platforms/platforms.module.ts
  5. 1
      apps/api/src/app/portfolio/portfolio.service.ts
  6. 26
      apps/client/src/app/components/admin-users/admin-users.component.ts
  7. 6
      apps/client/src/app/components/admin-users/admin-users.html
  8. 11
      apps/client/src/app/components/portfolio-summary/portfolio-summary.component.ts
  9. 5
      apps/client/src/app/pages/admin/admin-page.routes.ts
  10. 4
      apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts
  11. 6
      apps/client/src/app/services/data.service.ts
  12. 6
      apps/client/src/locales/messages.de.xlf
  13. 2
      libs/common/src/lib/interfaces/index.ts
  14. 3
      libs/common/src/lib/interfaces/info-item.interface.ts
  15. 1
      libs/common/src/lib/interfaces/portfolio-summary.interface.ts
  16. 5
      libs/common/src/lib/interfaces/responses/platforms-response.interface.ts
  17. 2
      libs/common/src/lib/permissions.ts

7
CHANGELOG.md

@ -7,9 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased ## Unreleased
### Added
- Added a new endpoint to get all platforms (`GET api/v1/platforms`)
### Changed ### Changed
- Improved the routing of the user detail dialog in the users section of the admin control panel
- Lifted the asset profile identifier editing restriction for `MANUAL` data sources in the asset profile details dialog of the admin control panel - Lifted the asset profile identifier editing restriction for `MANUAL` data sources in the asset profile details dialog of the admin control panel
- Improved the language localization for German (`de`)
- Upgraded `angular` from version `20.2.4` to `20.3.9` - Upgraded `angular` from version `20.2.4` to `20.3.9`
- Upgraded `ng-extract-i18n-merge` from `3.1.0` to `3.2.1` - Upgraded `ng-extract-i18n-merge` from `3.1.0` to `3.2.1`
- Upgraded `Nx` from version `21.5.1` to `22.1.3` - Upgraded `Nx` from version `21.5.1` to `22.1.3`
@ -19,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed ### Fixed
- Added the missing currency suffix to the cash balance field in the create or update account dialog - Added the missing currency suffix to the cash balance field in the create or update account dialog
- Fixed the time in market display of the portfolio summary tab on the home page for the impersonation mode
- Fixed the delete button in the asset profile details dialog of the admin control panel by providing the missing `watchedByCount` parameter - Fixed the delete button in the asset profile details dialog of the admin control panel by providing the missing `watchedByCount` parameter
## 2.224.2 - 2025-12-20 ## 2.224.2 - 2025-12-20

2
apps/api/src/app/app.module.ts

@ -37,6 +37,7 @@ import { AssetsModule } from './endpoints/assets/assets.module';
import { BenchmarksModule } from './endpoints/benchmarks/benchmarks.module'; import { BenchmarksModule } from './endpoints/benchmarks/benchmarks.module';
import { GhostfolioModule } from './endpoints/data-providers/ghostfolio/ghostfolio.module'; import { GhostfolioModule } from './endpoints/data-providers/ghostfolio/ghostfolio.module';
import { MarketDataModule } from './endpoints/market-data/market-data.module'; import { MarketDataModule } from './endpoints/market-data/market-data.module';
import { PlatformsModule } from './endpoints/platforms/platforms.module';
import { PublicModule } from './endpoints/public/public.module'; import { PublicModule } from './endpoints/public/public.module';
import { SitemapModule } from './endpoints/sitemap/sitemap.module'; import { SitemapModule } from './endpoints/sitemap/sitemap.module';
import { TagsModule } from './endpoints/tags/tags.module'; import { TagsModule } from './endpoints/tags/tags.module';
@ -95,6 +96,7 @@ import { UserModule } from './user/user.module';
MarketDataModule, MarketDataModule,
OrderModule, OrderModule,
PlatformModule, PlatformModule,
PlatformsModule,
PortfolioModule, PortfolioModule,
PortfolioSnapshotQueueModule, PortfolioSnapshotQueueModule,
PrismaModule, PrismaModule,

22
apps/api/src/app/endpoints/platforms/platforms.controller.ts

@ -0,0 +1,22 @@
import { PlatformService } from '@ghostfolio/api/app/platform/platform.service';
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
import { PlatformsResponse } from '@ghostfolio/common/interfaces';
import { permissions } from '@ghostfolio/common/permissions';
import { Controller, Get, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Controller('platforms')
export class PlatformsController {
public constructor(private readonly platformService: PlatformService) {}
@Get()
@HasPermission(permissions.readPlatforms)
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async getPlatforms(): Promise<PlatformsResponse> {
const platforms = await this.platformService.getPlatforms();
return { platforms };
}
}

11
apps/api/src/app/endpoints/platforms/platforms.module.ts

@ -0,0 +1,11 @@
import { PlatformModule } from '@ghostfolio/api/app/platform/platform.module';
import { Module } from '@nestjs/common';
import { PlatformsController } from './platforms.controller';
@Module({
controllers: [PlatformsController],
imports: [PlatformModule]
})
export class PlatformsModule {}

1
apps/api/src/app/portfolio/portfolio.service.ts

@ -1954,6 +1954,7 @@ export class PortfolioService {
}).length, }).length,
committedFunds: committedFunds.toNumber(), committedFunds: committedFunds.toNumber(),
currentValueInBaseCurrency: currentValueInBaseCurrency.toNumber(), currentValueInBaseCurrency: currentValueInBaseCurrency.toNumber(),
dateOfFirstActivity: firstOrderDate,
dividendInBaseCurrency: dividendInBaseCurrency.toNumber(), dividendInBaseCurrency: dividendInBaseCurrency.toNumber(),
emergencyFund: { emergencyFund: {
assets: emergencyFundHoldingsValueInBaseCurrency, assets: emergencyFundHoldingsValueInBaseCurrency,

26
apps/client/src/app/components/admin-users/admin-users.component.ts

@ -18,6 +18,7 @@ import {
User User
} from '@ghostfolio/common/interfaces'; } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { internalRoutes } from '@ghostfolio/common/routes/routes';
import { NotificationService } from '@ghostfolio/ui/notifications'; import { NotificationService } from '@ghostfolio/ui/notifications';
import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator';
import { GfValueComponent } from '@ghostfolio/ui/value'; import { GfValueComponent } from '@ghostfolio/ui/value';
@ -39,7 +40,7 @@ import {
PageEvent PageEvent
} from '@angular/material/paginator'; } from '@angular/material/paginator';
import { MatTableDataSource, MatTableModule } from '@angular/material/table'; import { MatTableDataSource, MatTableModule } from '@angular/material/table';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router, RouterModule } from '@angular/router';
import { IonIcon } from '@ionic/angular/standalone'; import { IonIcon } from '@ionic/angular/standalone';
import { import {
differenceInSeconds, differenceInSeconds,
@ -69,7 +70,8 @@ import { takeUntil } from 'rxjs/operators';
MatMenuModule, MatMenuModule,
MatPaginatorModule, MatPaginatorModule,
MatTableModule, MatTableModule,
NgxSkeletonLoaderModule NgxSkeletonLoaderModule,
RouterModule
], ],
selector: 'gf-admin-users', selector: 'gf-admin-users',
styleUrls: ['./admin-users.scss'], styleUrls: ['./admin-users.scss'],
@ -88,6 +90,8 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit {
public info: InfoItem; public info: InfoItem;
public isLoading = false; public isLoading = false;
public pageSize = DEFAULT_PAGE_SIZE; public pageSize = DEFAULT_PAGE_SIZE;
public routerLinkAdminControlUsers =
internalRoutes.adminControl.subRoutes.users.routerLink;
public totalItems = 0; public totalItems = 0;
public user: User; public user: User;
@ -136,11 +140,13 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit {
]; ];
} }
this.route.queryParams this.route.paramMap
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe((params) => { .subscribe((params) => {
if (params['userDetailDialog'] && params['userId']) { const userId = params.get('userId');
this.openUserDetailDialog(params['userId']);
if (userId) {
this.openUserDetailDialog(userId);
} }
}); });
@ -248,9 +254,9 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit {
} }
public onOpenUserDetailDialog(userId: string) { public onOpenUserDetailDialog(userId: string) {
this.router.navigate([], { this.router.navigate(
queryParams: { userId, userDetailDialog: true } internalRoutes.adminControl.subRoutes.users.routerLink.concat(userId)
}); );
} }
public ngOnDestroy() { public ngOnDestroy() {
@ -301,7 +307,9 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit {
.afterClosed() .afterClosed()
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe(() => { .subscribe(() => {
this.router.navigate(['.'], { relativeTo: this.route }); this.router.navigate(
internalRoutes.adminControl.subRoutes.users.routerLink
);
}); });
} }
} }

6
apps/client/src/app/components/admin-users/admin-users.html

@ -215,9 +215,9 @@
<ion-icon name="ellipsis-horizontal" /> <ion-icon name="ellipsis-horizontal" />
</button> </button>
<mat-menu #userMenu="matMenu" xPosition="before"> <mat-menu #userMenu="matMenu" xPosition="before">
<button <a
mat-menu-item mat-menu-item
(click)="onOpenUserDetailDialog(element.id)" [routerLink]="routerLinkAdminControlUsers.concat(element.id)"
> >
<span class="align-items-center d-flex"> <span class="align-items-center d-flex">
<ion-icon class="mr-2" name="person-outline" /> <ion-icon class="mr-2" name="person-outline" />
@ -225,7 +225,7 @@
><ng-container i18n>View Details</ng-container>...</span ><ng-container i18n>View Details</ng-container>...</span
> >
</span> </span>
</button> </a>
@if (hasPermissionToImpersonateAllUsers) { @if (hasPermissionToImpersonateAllUsers) {
<button mat-menu-item (click)="onImpersonateUser(element.id)"> <button mat-menu-item (click)="onImpersonateUser(element.id)">
<span class="align-items-center d-flex"> <span class="align-items-center d-flex">

11
apps/client/src/app/components/portfolio-summary/portfolio-summary.component.ts

@ -84,10 +84,13 @@ export class GfPortfolioSummaryComponent implements OnChanges {
this.precision = 0; this.precision = 0;
} }
if (this.user.dateOfFirstActivity) { if (this.summary.dateOfFirstActivity) {
this.timeInMarket = formatDistanceToNow(this.user.dateOfFirstActivity, { this.timeInMarket = formatDistanceToNow(
locale: getDateFnsLocale(this.language) this.summary.dateOfFirstActivity,
}); {
locale: getDateFnsLocale(this.language)
}
);
} else { } else {
this.timeInMarket = '-'; this.timeInMarket = '-';
} }

5
apps/client/src/app/pages/admin/admin-page.routes.ts

@ -38,6 +38,11 @@ export const routes: Routes = [
path: internalRoutes.adminControl.subRoutes.users.path, path: internalRoutes.adminControl.subRoutes.users.path,
component: GfAdminUsersComponent, component: GfAdminUsersComponent,
title: internalRoutes.adminControl.subRoutes.users.title title: internalRoutes.adminControl.subRoutes.users.title
},
{
path: `${internalRoutes.adminControl.subRoutes.users.path}/:userId`,
component: GfAdminUsersComponent,
title: internalRoutes.adminControl.subRoutes.users.title
} }
], ],
component: AdminPageComponent, component: AdminPageComponent,

4
apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts

@ -96,7 +96,6 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy {
public isLoading = false; public isLoading = false;
public isToday = isToday; public isToday = isToday;
public mode: 'create' | 'update'; public mode: 'create' | 'update';
public platforms: { id: string; name: string }[];
public tagsAvailable: Tag[] = []; public tagsAvailable: Tag[] = [];
public total = 0; public total = 0;
public typesTranslationMap = new Map<Type, string>(); public typesTranslationMap = new Map<Type, string>();
@ -127,11 +126,10 @@ export class GfCreateOrUpdateActivityDialogComponent implements OnDestroy {
this.dateAdapter.setLocale(this.locale); this.dateAdapter.setLocale(this.locale);
const { currencies, platforms } = this.dataService.fetchInfo(); const { currencies } = this.dataService.fetchInfo();
this.currencies = currencies; this.currencies = currencies;
this.defaultDateFormat = getDateFormatString(this.locale); this.defaultDateFormat = getDateFormatString(this.locale);
this.platforms = platforms;
this.dataService this.dataService
.fetchPortfolioHoldings() .fetchPortfolioHoldings()

6
apps/client/src/app/services/data.service.ts

@ -564,6 +564,12 @@ export class DataService {
} }
} }
if (response.summary?.dateOfFirstActivity) {
response.summary.dateOfFirstActivity = parseISO(
response.summary.dateOfFirstActivity
);
}
return response; return response;
}) })
); );

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

@ -3475,7 +3475,7 @@
</trans-unit> </trans-unit>
<trans-unit id="5499742151525073097" datatype="html"> <trans-unit id="5499742151525073097" datatype="html">
<source>Upgrade Plan</source> <source>Upgrade Plan</source>
<target state="translated">Abonnement abschliessen</target> <target state="translated">Mitgliedschaft abschliessen</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/header/header.component.html</context> <context context-type="sourcefile">apps/client/src/app/components/header/header.component.html</context>
<context context-type="linenumber">193</context> <context context-type="linenumber">193</context>
@ -3747,7 +3747,7 @@
</trans-unit> </trans-unit>
<trans-unit id="2030314101752312029" datatype="html"> <trans-unit id="2030314101752312029" datatype="html">
<source>Renew Plan</source> <source>Renew Plan</source>
<target state="translated">Abonnement erneuern</target> <target state="translated">Mitgliedschaft erneuern</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/components/header/header.component.html</context> <context context-type="sourcefile">apps/client/src/app/components/header/header.component.html</context>
<context context-type="linenumber">191</context> <context context-type="linenumber">191</context>
@ -6934,7 +6934,7 @@
</trans-unit> </trans-unit>
<trans-unit id="7522916136412124285" datatype="html"> <trans-unit id="7522916136412124285" datatype="html">
<source>to use our referral link and get a Ghostfolio Premium membership for one year</source> <source>to use our referral link and get a Ghostfolio Premium membership for one year</source>
<target state="translated">um unseren Empfehlungslink zu verwenden und ein Ghostfolio Premium-Abonnement für ein Jahr zu erhalten</target> <target state="translated">um unseren Empfehlungslink zu verwenden und eine Ghostfolio Premium-Mitgliedschaft für ein Jahr zu erhalten</target>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">apps/client/src/app/pages/pricing/pricing-page.html</context> <context context-type="sourcefile">apps/client/src/app/pages/pricing/pricing-page.html</context>
<context context-type="linenumber">343</context> <context context-type="linenumber">343</context>

2
libs/common/src/lib/interfaces/index.ts

@ -68,6 +68,7 @@ import type { LookupResponse } from './responses/lookup-response.interface';
import type { MarketDataDetailsResponse } from './responses/market-data-details-response.interface'; import type { MarketDataDetailsResponse } from './responses/market-data-details-response.interface';
import type { MarketDataOfMarketsResponse } from './responses/market-data-of-markets-response.interface'; import type { MarketDataOfMarketsResponse } from './responses/market-data-of-markets-response.interface';
import type { OAuthResponse } from './responses/oauth-response.interface'; import type { OAuthResponse } from './responses/oauth-response.interface';
import type { PlatformsResponse } from './responses/platforms-response.interface';
import type { PortfolioDividendsResponse } from './responses/portfolio-dividends-response.interface'; import type { PortfolioDividendsResponse } from './responses/portfolio-dividends-response.interface';
import type { PortfolioHoldingResponse } from './responses/portfolio-holding-response.interface'; import type { PortfolioHoldingResponse } from './responses/portfolio-holding-response.interface';
import type { PortfolioHoldingsResponse } from './responses/portfolio-holdings-response.interface'; import type { PortfolioHoldingsResponse } from './responses/portfolio-holdings-response.interface';
@ -158,6 +159,7 @@ export {
MarketDataDetailsResponse, MarketDataDetailsResponse,
MarketDataOfMarketsResponse, MarketDataOfMarketsResponse,
OAuthResponse, OAuthResponse,
PlatformsResponse,
PortfolioChart, PortfolioChart,
PortfolioDetails, PortfolioDetails,
PortfolioDividendsResponse, PortfolioDividendsResponse,

3
libs/common/src/lib/interfaces/info-item.interface.ts

@ -13,7 +13,10 @@ export interface InfoItem {
globalPermissions: string[]; globalPermissions: string[];
isDataGatheringEnabled?: string; isDataGatheringEnabled?: string;
isReadOnlyMode?: boolean; isReadOnlyMode?: boolean;
/** @deprecated */
platforms: Platform[]; platforms: Platform[];
statistics: Statistics; statistics: Statistics;
stripePublicKey?: string; stripePublicKey?: string;
subscriptionOffer?: SubscriptionOffer; subscriptionOffer?: SubscriptionOffer;

1
libs/common/src/lib/interfaces/portfolio-summary.interface.ts

@ -7,6 +7,7 @@ export interface PortfolioSummary extends PortfolioPerformance {
annualizedPerformancePercentWithCurrencyEffect: number; annualizedPerformancePercentWithCurrencyEffect: number;
cash: number; cash: number;
committedFunds: number; committedFunds: number;
dateOfFirstActivity: Date;
dividendInBaseCurrency: number; dividendInBaseCurrency: number;
emergencyFund: { emergencyFund: {
assets: number; assets: number;

5
libs/common/src/lib/interfaces/responses/platforms-response.interface.ts

@ -0,0 +1,5 @@
import { Platform } from '@prisma/client';
export interface PlatformsResponse {
platforms: Platform[];
}

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

@ -93,6 +93,7 @@ export function getPermissions(aRole: Role): string[] {
permissions.readAiPrompt, permissions.readAiPrompt,
permissions.readMarketData, permissions.readMarketData,
permissions.readMarketDataOfOwnAssetProfile, permissions.readMarketDataOfOwnAssetProfile,
permissions.readPlatforms,
permissions.readPlatformsWithAccountCount, permissions.readPlatformsWithAccountCount,
permissions.readTags, permissions.readTags,
permissions.readWatchlist, permissions.readWatchlist,
@ -136,6 +137,7 @@ export function getPermissions(aRole: Role): string[] {
permissions.deleteWatchlistItem, permissions.deleteWatchlistItem,
permissions.readAiPrompt, permissions.readAiPrompt,
permissions.readMarketDataOfOwnAssetProfile, permissions.readMarketDataOfOwnAssetProfile,
permissions.readPlatforms,
permissions.readWatchlist, permissions.readWatchlist,
permissions.updateAccount, permissions.updateAccount,
permissions.updateAccess, permissions.updateAccess,

Loading…
Cancel
Save