David Requeno 2 days ago
committed by GitHub
parent
commit
fdf45dc994
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 4
      CHANGELOG.md
  2. 47
      apps/api/src/app/endpoints/public/public.controller.ts
  3. 14
      apps/client/src/app/pages/public/public-page.component.ts
  4. 23
      apps/client/src/app/pages/public/public-page.html
  5. 14
      libs/common/src/lib/interfaces/responses/public-portfolio-response.interface.ts

4
CHANGELOG.md

@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased ## Unreleased
### Added
- Added the latest activities to the public page
### Changed ### Changed
- Refreshed the cryptocurrencies list - Refreshed the cryptocurrencies list

47
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 { DEFAULT_CURRENCY } from '@ghostfolio/common/config';
import { getSum } from '@ghostfolio/common/helper'; import { getSum } from '@ghostfolio/common/helper';
import { PublicPortfolioResponse } from '@ghostfolio/common/interfaces'; import { PublicPortfolioResponse } from '@ghostfolio/common/interfaces';
@ -21,18 +15,29 @@ import { REQUEST } from '@nestjs/core';
import { Big } from 'big.js'; import { Big } from 'big.js';
import { StatusCodes, getReasonPhrase } from 'http-status-codes'; 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') @Controller('public')
export class PublicController { export class PublicController {
public constructor( public constructor(
private readonly accessService: AccessService, private readonly accessService: AccessService,
private readonly configurationService: ConfigurationService, private readonly configurationService: ConfigurationService,
private readonly exchangeRateDataService: ExchangeRateDataService, private readonly exchangeRateDataService: ExchangeRateDataService,
private readonly orderService: OrderService,
private readonly portfolioService: PortfolioService, private readonly portfolioService: PortfolioService,
@Inject(REQUEST) private readonly request: RequestWithUser, @Inject(REQUEST) private readonly request: RequestWithUser,
private readonly userService: UserService private readonly userService: UserService
) {} ) {}
@Get(':accessId/portfolio') @Get(':accessId/portfolio')
@UseInterceptors(RedactValuesInResponseInterceptor)
@UseInterceptors(TransformDataSourceInResponseInterceptor) @UseInterceptors(TransformDataSourceInResponseInterceptor)
public async getPublicPortfolio( public async getPublicPortfolio(
@Param('accessId') accessId @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) => { Object.values(markets ?? {}).forEach((market) => {
delete market.valueInBaseCurrency; delete market.valueInBaseCurrency;
}); });
@ -83,6 +117,7 @@ export class PublicController {
const publicPortfolioResponse: PublicPortfolioResponse = { const publicPortfolioResponse: PublicPortfolioResponse = {
createdAt, createdAt,
hasDetails, hasDetails,
latestActivities,
markets, markets,
alias: access.alias, alias: access.alias,
holdings: {}, holdings: {},

14
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 { UNKNOWN_KEY } from '@ghostfolio/common/config';
import { prettifySymbol } from '@ghostfolio/common/helper'; import { prettifySymbol } from '@ghostfolio/common/helper';
import { import {
@ -6,6 +6,7 @@ import {
PublicPortfolioResponse PublicPortfolioResponse
} from '@ghostfolio/common/interfaces'; } from '@ghostfolio/common/interfaces';
import { Market } from '@ghostfolio/common/types'; 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 { GfHoldingsTableComponent } from '@ghostfolio/ui/holdings-table/holdings-table.component';
import { GfPortfolioProportionChartComponent } from '@ghostfolio/ui/portfolio-proportion-chart/portfolio-proportion-chart.component'; import { GfPortfolioProportionChartComponent } from '@ghostfolio/ui/portfolio-proportion-chart/portfolio-proportion-chart.component';
import { GfValueComponent } from '@ghostfolio/ui/value'; import { GfValueComponent } from '@ghostfolio/ui/value';
@ -20,6 +21,7 @@ import {
} from '@angular/core'; } from '@angular/core';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card'; import { MatCardModule } from '@angular/material/card';
import { MatTableDataSource } from '@angular/material/table';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { AssetClass } from '@prisma/client'; import { AssetClass } from '@prisma/client';
import { StatusCodes } from 'http-status-codes'; import { StatusCodes } from 'http-status-codes';
@ -28,10 +30,13 @@ import { DeviceDetectorService } from 'ngx-device-detector';
import { EMPTY, Subject } from 'rxjs'; import { EMPTY, Subject } from 'rxjs';
import { catchError, takeUntil } from 'rxjs/operators'; import { catchError, takeUntil } from 'rxjs/operators';
import { DataService } from '../../services/data.service';
@Component({ @Component({
host: { class: 'page' }, host: { class: 'page' },
imports: [ imports: [
CommonModule, CommonModule,
GfActivitiesTableComponent,
GfHoldingsTableComponent, GfHoldingsTableComponent,
GfPortfolioProportionChartComponent, GfPortfolioProportionChartComponent,
GfValueComponent, GfValueComponent,
@ -54,9 +59,11 @@ export class GfPublicPageComponent implements OnInit {
public defaultAlias = $localize`someone`; public defaultAlias = $localize`someone`;
public deviceType: string; public deviceType: string;
public holdings: PublicPortfolioResponse['holdings'][string][]; public holdings: PublicPortfolioResponse['holdings'][string][];
public latestActivitiesDataSource: MatTableDataSource<Activity>;
public markets: { public markets: {
[key in Market]: { id: Market; valueInPercentage: number }; [key in Market]: { id: Market; valueInPercentage: number };
}; };
public maxSafeInteger = Number.MAX_SAFE_INTEGER;
public positions: { public positions: {
[symbol: string]: Pick<PortfolioPosition, 'currency' | 'name'> & { [symbol: string]: Pick<PortfolioPosition, 'currency' | 'name'> & {
value: number; value: number;
@ -105,6 +112,11 @@ export class GfPublicPageComponent implements OnInit {
.subscribe((portfolioPublicDetails) => { .subscribe((portfolioPublicDetails) => {
this.publicPortfolioDetails = portfolioPublicDetails; this.publicPortfolioDetails = portfolioPublicDetails;
this.latestActivitiesDataSource = new MatTableDataSource(
(this.publicPortfolioDetails.latestActivities ||
[]) as unknown as Activity[]
);
this.initializeAnalysisData(); this.initializeAnalysisData();
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();

23
apps/client/src/app/pages/public/public-page.html

@ -203,6 +203,29 @@
</div> </div>
</div> </div>
} }
<div class="row">
<div class="col-md-12">
<mat-card appearance="outlined" class="mb-3">
<mat-card-header class="overflow-hidden w-100">
<mat-card-title class="text-truncate" i18n
>Latest activities</mat-card-title
>
</mat-card-header>
<mat-card-content>
<gf-activities-table
[dataSource]="latestActivitiesDataSource"
[deviceType]="deviceType"
[hasPermissionToCreateActivity]="false"
[hasPermissionToDeleteActivity]="false"
[hasPermissionToExportActivities]="false"
[hasPermissionToOpenDetails]="false"
[pageSize]="maxSafeInteger"
[showActions]="false"
/>
</mat-card-content>
</mat-card>
</div>
</div>
<div class="row my-5"> <div class="row my-5">
<div class="col-md-10 offset-md-1"> <div class="col-md-10 offset-md-1">
<h2 class="h4 mb-1 text-center" i18n> <h2 class="h4 mb-1 text-center" i18n>

14
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 { Market } from '../../types';
import type { AccountWithPlatform } from '../../types';
export interface PublicPortfolioResponse extends PublicPortfolioResponseV1 { export interface PublicPortfolioResponse extends PublicPortfolioResponseV1 {
alias?: string; alias?: string;
@ -23,6 +26,15 @@ export interface PublicPortfolioResponse extends PublicPortfolioResponseV1 {
| 'valueInPercentage' | 'valueInPercentage'
>; >;
}; };
latestActivities: (Pick<
Order,
'currency' | 'date' | 'quantity' | 'type' | 'unitPrice'
> & {
account?: Pick<AccountWithPlatform, 'currency' | 'name' | 'platform'>;
SymbolProfile?: EnhancedSymbolProfile;
value: number;
valueInBaseCurrency: number;
})[];
markets: { markets: {
[key in Market]: Pick< [key in Market]: Pick<
PortfolioDetails['markets'][key], PortfolioDetails['markets'][key],

Loading…
Cancel
Save