diff --git a/CHANGELOG.md b/CHANGELOG.md index e6215adab..185ba04ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- Added the latest activities to the public page + ### Changed - Refreshed the cryptocurrencies list diff --git a/apps/api/src/app/endpoints/public/public.controller.ts b/apps/api/src/app/endpoints/public/public.controller.ts index 0175b6ce8..eb7b5238c 100644 --- a/apps/api/src/app/endpoints/public/public.controller.ts +++ b/apps/api/src/app/endpoints/public/public.controller.ts @@ -1,9 +1,3 @@ -import { AccessService } from '@ghostfolio/api/app/access/access.service'; -import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service'; -import { UserService } from '@ghostfolio/api/app/user/user.service'; -import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor'; -import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service'; -import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service'; import { DEFAULT_CURRENCY } from '@ghostfolio/common/config'; import { getSum } from '@ghostfolio/common/helper'; import { PublicPortfolioResponse } from '@ghostfolio/common/interfaces'; @@ -21,18 +15,29 @@ import { REQUEST } from '@nestjs/core'; import { Big } from 'big.js'; import { StatusCodes, getReasonPhrase } from 'http-status-codes'; +import { RedactValuesInResponseInterceptor } from '../../../interceptors/redact-values-in-response/redact-values-in-response.interceptor'; +import { TransformDataSourceInResponseInterceptor } from '../../../interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor'; +import { ConfigurationService } from '../../../services/configuration/configuration.service'; +import { ExchangeRateDataService } from '../../../services/exchange-rate-data/exchange-rate-data.service'; +import { AccessService } from '../../access/access.service'; +import { OrderService } from '../../order/order.service'; +import { PortfolioService } from '../../portfolio/portfolio.service'; +import { UserService } from '../../user/user.service'; + @Controller('public') export class PublicController { public constructor( private readonly accessService: AccessService, private readonly configurationService: ConfigurationService, private readonly exchangeRateDataService: ExchangeRateDataService, + private readonly orderService: OrderService, private readonly portfolioService: PortfolioService, @Inject(REQUEST) private readonly request: RequestWithUser, private readonly userService: UserService ) {} @Get(':accessId/portfolio') + @UseInterceptors(RedactValuesInResponseInterceptor) @UseInterceptors(TransformDataSourceInResponseInterceptor) public async getPublicPortfolio( @Param('accessId') accessId @@ -76,6 +81,35 @@ export class PublicController { }) ]); + const latestActivitiesPromise = this.orderService.getOrders({ + includeDrafts: false, + sortColumn: 'date', + sortDirection: 'desc', + take: 10, + userCurrency: user.settings?.settings.baseCurrency ?? DEFAULT_CURRENCY, + userId: user.id, + withExcludedAccountsAndActivities: false + }); + + const { activities } = await latestActivitiesPromise; + const latestActivities = activities.map((a) => ({ + account: a.account + ? { + currency: a.account.currency, + name: a.account.name, + platform: a.account.platform + } + : undefined, + currency: a.currency, + date: a.date, + quantity: a.quantity, + SymbolProfile: a.SymbolProfile, + type: a.type, + unitPrice: a.unitPrice, + value: a.value, + valueInBaseCurrency: a.valueInBaseCurrency + })); + Object.values(markets ?? {}).forEach((market) => { delete market.valueInBaseCurrency; }); @@ -83,6 +117,7 @@ export class PublicController { const publicPortfolioResponse: PublicPortfolioResponse = { createdAt, hasDetails, + latestActivities, markets, alias: access.alias, holdings: {}, diff --git a/apps/client/src/app/pages/public/public-page.component.ts b/apps/client/src/app/pages/public/public-page.component.ts index ea11dd25f..e7b24c2ab 100644 --- a/apps/client/src/app/pages/public/public-page.component.ts +++ b/apps/client/src/app/pages/public/public-page.component.ts @@ -1,4 +1,4 @@ -import { DataService } from '@ghostfolio/client/services/data.service'; +import type { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { UNKNOWN_KEY } from '@ghostfolio/common/config'; import { prettifySymbol } from '@ghostfolio/common/helper'; import { @@ -6,6 +6,7 @@ import { PublicPortfolioResponse } from '@ghostfolio/common/interfaces'; import { Market } from '@ghostfolio/common/types'; +import { GfActivitiesTableComponent } from '@ghostfolio/ui/activities-table/activities-table.component'; import { GfHoldingsTableComponent } from '@ghostfolio/ui/holdings-table/holdings-table.component'; import { GfPortfolioProportionChartComponent } from '@ghostfolio/ui/portfolio-proportion-chart/portfolio-proportion-chart.component'; import { GfValueComponent } from '@ghostfolio/ui/value'; @@ -20,6 +21,7 @@ import { } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; +import { MatTableDataSource } from '@angular/material/table'; import { ActivatedRoute, Router } from '@angular/router'; import { AssetClass } from '@prisma/client'; import { StatusCodes } from 'http-status-codes'; @@ -28,10 +30,13 @@ import { DeviceDetectorService } from 'ngx-device-detector'; import { EMPTY, Subject } from 'rxjs'; import { catchError, takeUntil } from 'rxjs/operators'; +import { DataService } from '../../services/data.service'; + @Component({ host: { class: 'page' }, imports: [ CommonModule, + GfActivitiesTableComponent, GfHoldingsTableComponent, GfPortfolioProportionChartComponent, GfValueComponent, @@ -54,9 +59,11 @@ export class GfPublicPageComponent implements OnInit { public defaultAlias = $localize`someone`; public deviceType: string; public holdings: PublicPortfolioResponse['holdings'][string][]; + public latestActivitiesDataSource: MatTableDataSource; public markets: { [key in Market]: { id: Market; valueInPercentage: number }; }; + public maxSafeInteger = Number.MAX_SAFE_INTEGER; public positions: { [symbol: string]: Pick & { value: number; @@ -105,6 +112,11 @@ export class GfPublicPageComponent implements OnInit { .subscribe((portfolioPublicDetails) => { this.publicPortfolioDetails = portfolioPublicDetails; + this.latestActivitiesDataSource = new MatTableDataSource( + (this.publicPortfolioDetails.latestActivities || + []) as unknown as Activity[] + ); + this.initializeAnalysisData(); this.changeDetectorRef.markForCheck(); diff --git a/apps/client/src/app/pages/public/public-page.html b/apps/client/src/app/pages/public/public-page.html index 004149ca3..3bf14ae59 100644 --- a/apps/client/src/app/pages/public/public-page.html +++ b/apps/client/src/app/pages/public/public-page.html @@ -203,6 +203,29 @@ } +
+
+ + + Latest activities + + + + + +
+

diff --git a/libs/common/src/lib/interfaces/responses/public-portfolio-response.interface.ts b/libs/common/src/lib/interfaces/responses/public-portfolio-response.interface.ts index 7a98e3c8d..7c1014983 100644 --- a/libs/common/src/lib/interfaces/responses/public-portfolio-response.interface.ts +++ b/libs/common/src/lib/interfaces/responses/public-portfolio-response.interface.ts @@ -1,5 +1,8 @@ -import { PortfolioDetails, PortfolioPosition } from '..'; +import type { Order } from '@prisma/client'; + +import { EnhancedSymbolProfile, PortfolioDetails, PortfolioPosition } from '..'; import { Market } from '../../types'; +import type { AccountWithPlatform } from '../../types'; export interface PublicPortfolioResponse extends PublicPortfolioResponseV1 { alias?: string; @@ -23,6 +26,15 @@ export interface PublicPortfolioResponse extends PublicPortfolioResponseV1 { | 'valueInPercentage' >; }; + latestActivities: (Pick< + Order, + 'currency' | 'date' | 'quantity' | 'type' | 'unitPrice' + > & { + account?: Pick; + SymbolProfile?: EnhancedSymbolProfile; + value: number; + valueInBaseCurrency: number; + })[]; markets: { [key in Market]: Pick< PortfolioDetails['markets'][key],