diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f680ffa7..dda83eaab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,17 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- Added a new endpoint to get all platforms (`GET api/v1/platforms`) + ### 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 +- Improved the language localization for German (`de`) - Upgraded `angular` from version `20.2.4` to `20.3.9` - Upgraded `class-validator` from version `0.14.2` to `0.14.3` -- Upgraded `Nx` from version `21.5.1` to `22.0.4` +- 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 `shx` from version `0.3.4` to `0.4.0` +- Upgraded `storybook` from version `9.1.5` to `10.1.10` ### Fixed - 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 ## 2.224.2 - 2025-12-20 diff --git a/apps/api/src/app/app.module.ts b/apps/api/src/app/app.module.ts index 5ec148558..89f52e1ea 100644 --- a/apps/api/src/app/app.module.ts +++ b/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 { GhostfolioModule } from './endpoints/data-providers/ghostfolio/ghostfolio.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 { SitemapModule } from './endpoints/sitemap/sitemap.module'; import { TagsModule } from './endpoints/tags/tags.module'; @@ -95,6 +96,7 @@ import { UserModule } from './user/user.module'; MarketDataModule, OrderModule, PlatformModule, + PlatformsModule, PortfolioModule, PortfolioSnapshotQueueModule, PrismaModule, diff --git a/apps/api/src/app/endpoints/platforms/platforms.controller.ts b/apps/api/src/app/endpoints/platforms/platforms.controller.ts new file mode 100644 index 000000000..46303a3f8 --- /dev/null +++ b/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 { + const platforms = await this.platformService.getPlatforms(); + + return { platforms }; + } +} diff --git a/apps/api/src/app/endpoints/platforms/platforms.module.ts b/apps/api/src/app/endpoints/platforms/platforms.module.ts new file mode 100644 index 000000000..21d0edf69 --- /dev/null +++ b/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 {} diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index 084c8f4ed..faabee79b 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -1954,6 +1954,7 @@ export class PortfolioService { }).length, committedFunds: committedFunds.toNumber(), currentValueInBaseCurrency: currentValueInBaseCurrency.toNumber(), + dateOfFirstActivity: firstOrderDate, dividendInBaseCurrency: dividendInBaseCurrency.toNumber(), emergencyFund: { assets: emergencyFundHoldingsValueInBaseCurrency, diff --git a/apps/client/src/app/components/admin-users/admin-users.component.ts b/apps/client/src/app/components/admin-users/admin-users.component.ts index 99fbe7901..1722b498f 100644 --- a/apps/client/src/app/components/admin-users/admin-users.component.ts +++ b/apps/client/src/app/components/admin-users/admin-users.component.ts @@ -18,6 +18,7 @@ import { User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; +import { internalRoutes } from '@ghostfolio/common/routes/routes'; import { NotificationService } from '@ghostfolio/ui/notifications'; import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator'; import { GfValueComponent } from '@ghostfolio/ui/value'; @@ -39,7 +40,7 @@ import { PageEvent } from '@angular/material/paginator'; 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 { differenceInSeconds, @@ -69,7 +70,8 @@ import { takeUntil } from 'rxjs/operators'; MatMenuModule, MatPaginatorModule, MatTableModule, - NgxSkeletonLoaderModule + NgxSkeletonLoaderModule, + RouterModule ], selector: 'gf-admin-users', styleUrls: ['./admin-users.scss'], @@ -88,6 +90,8 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { public info: InfoItem; public isLoading = false; public pageSize = DEFAULT_PAGE_SIZE; + public routerLinkAdminControlUsers = + internalRoutes.adminControl.subRoutes.users.routerLink; public totalItems = 0; public user: User; @@ -136,11 +140,13 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { ]; } - this.route.queryParams + this.route.paramMap .pipe(takeUntil(this.unsubscribeSubject)) .subscribe((params) => { - if (params['userDetailDialog'] && params['userId']) { - this.openUserDetailDialog(params['userId']); + const userId = params.get('userId'); + + if (userId) { + this.openUserDetailDialog(userId); } }); @@ -248,9 +254,9 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { } public onOpenUserDetailDialog(userId: string) { - this.router.navigate([], { - queryParams: { userId, userDetailDialog: true } - }); + this.router.navigate( + internalRoutes.adminControl.subRoutes.users.routerLink.concat(userId) + ); } public ngOnDestroy() { @@ -301,7 +307,9 @@ export class GfAdminUsersComponent implements OnDestroy, OnInit { .afterClosed() .pipe(takeUntil(this.unsubscribeSubject)) .subscribe(() => { - this.router.navigate(['.'], { relativeTo: this.route }); + this.router.navigate( + internalRoutes.adminControl.subRoutes.users.routerLink + ); }); } } diff --git a/apps/client/src/app/components/admin-users/admin-users.html b/apps/client/src/app/components/admin-users/admin-users.html index ebcdc6f5f..0f9789feb 100644 --- a/apps/client/src/app/components/admin-users/admin-users.html +++ b/apps/client/src/app/components/admin-users/admin-users.html @@ -215,9 +215,9 @@ - + @if (hasPermissionToImpersonateAllUsers) {