Browse Source

Refine asset profiles response structure

* Move the asset profile item type to the responses interfaces

* Rename the response payload from marketData to assetProfiles

* Align asset profile response mapping
pull/7056/head
Sjohn21 4 weeks ago
parent
commit
ed6824c5b9
  1. 129
      apps/api/src/app/endpoints/asset-profiles/asset-profiles.service.ts
  2. 10
      apps/client/src/app/components/admin-market-data/admin-market-data.component.ts
  3. 8
      libs/common/src/lib/interfaces/index.ts
  4. 12
      libs/common/src/lib/interfaces/responses/asset-profile-item.interface.ts
  5. 4
      libs/ui/src/lib/assistant/assistant.component.ts

129
apps/api/src/app/endpoints/asset-profiles/asset-profiles.service.ts

@ -160,7 +160,7 @@ export class AssetProfilesService {
}), }),
this.prismaService.symbolProfile.count({ where }) this.prismaService.symbolProfile.count({ where })
]); ]);
const assetProfiles = symbolProfileResult[0]; const symbolProfiles = symbolProfileResult[0];
let count = symbolProfileResult[1]; let count = symbolProfileResult[1];
const lastMarketPrices = await this.prismaService.marketData.findMany({ const lastMarketPrices = await this.prismaService.marketData.findMany({
@ -173,12 +173,12 @@ export class AssetProfilesService {
}, },
where: { where: {
dataSource: { dataSource: {
in: assetProfiles.map(({ dataSource }) => { in: symbolProfiles.map(({ dataSource }) => {
return dataSource; return dataSource;
}) })
}, },
symbol: { symbol: {
in: assetProfiles.map(({ symbol }) => { in: symbolProfiles.map(({ symbol }) => {
return symbol; return symbol;
}) })
} }
@ -194,8 +194,8 @@ export class AssetProfilesService {
); );
} }
let marketData: AssetProfileItem[] = await Promise.all( let assetProfiles: AssetProfileItem[] = await Promise.all(
assetProfiles.map(async (assetProfile) => { symbolProfiles.map(async (assetProfile) => {
const { const {
_count, _count,
activities, activities,
@ -239,8 +239,8 @@ export class AssetProfilesService {
currency, currency,
dataSource, dataSource,
id, id,
isin,
isActive, isActive,
isin,
lastMarketPrice, lastMarketPrice,
marketDataItemCount, marketDataItemCount,
name, name,
@ -256,21 +256,21 @@ export class AssetProfilesService {
if (presetId) { if (presetId) {
if (presetId === 'ETF_WITHOUT_COUNTRIES') { if (presetId === 'ETF_WITHOUT_COUNTRIES') {
marketData = marketData.filter(({ countriesCount }) => { assetProfiles = assetProfiles.filter(({ countriesCount }) => {
return countriesCount === 0; return countriesCount === 0;
}); });
} else if (presetId === 'ETF_WITHOUT_SECTORS') { } else if (presetId === 'ETF_WITHOUT_SECTORS') {
marketData = marketData.filter(({ sectorsCount }) => { assetProfiles = assetProfiles.filter(({ sectorsCount }) => {
return sectorsCount === 0; return sectorsCount === 0;
}); });
} }
count = marketData.length; count = assetProfiles.length;
} }
return { return {
count, assetProfiles,
marketData count
}; };
} }
@ -348,50 +348,6 @@ export class AssetProfilesService {
return data; return data;
} }
private getExtendedPrismaClient() {
const symbolProfileExtension = Prisma.defineExtension((client) => {
return client.$extends({
result: {
symbolProfile: {
isUsedByUsersWithSubscription: {
compute: async ({ id }) => {
const { _count } =
await this.prismaService.symbolProfile.findUnique({
select: {
_count: {
select: {
activities: {
where: {
user: {
subscriptions: {
some: {
expiresAt: {
gt: new Date()
}
}
}
}
}
}
}
}
},
where: {
id
}
});
return _count.activities > 0;
}
}
}
}
});
});
return this.prismaService.$extends(symbolProfileExtension);
}
private async getAssetProfilesForCurrencies(): Promise<AssetProfilesResponse> { private async getAssetProfilesForCurrencies(): Promise<AssetProfilesResponse> {
const currencyPairs = this.exchangeRateDataService.getCurrencyPairs(); const currencyPairs = this.exchangeRateDataService.getCurrencyPairs();
@ -432,7 +388,7 @@ export class AssetProfilesService {
); );
} }
const marketDataPromise: Promise<AssetProfileItem>[] = currencyPairs.map( const assetProfilePromises: Promise<AssetProfileItem>[] = currencyPairs.map(
async ({ dataSource, symbol }) => { async ({ dataSource, symbol }) => {
let activitiesCount: EnhancedSymbolProfile['activitiesCount'] = 0; let activitiesCount: EnhancedSymbolProfile['activitiesCount'] = 0;
let currency: EnhancedSymbolProfile['currency'] = '-'; let currency: EnhancedSymbolProfile['currency'] = '-';
@ -458,26 +414,71 @@ export class AssetProfilesService {
return { return {
activitiesCount, activitiesCount,
currency,
dataSource,
isin: null,
lastMarketPrice,
marketDataItemCount,
symbol,
assetClass: AssetClass.LIQUIDITY, assetClass: AssetClass.LIQUIDITY,
assetSubClass: AssetSubClass.CASH, assetSubClass: AssetSubClass.CASH,
comment: null,
countriesCount: 0, countriesCount: 0,
currency,
dataSource,
date: dateOfFirstActivity, date: dateOfFirstActivity,
id: undefined, id: undefined,
isActive: true, isActive: true,
isin: null,
lastMarketPrice,
marketDataItemCount,
name: symbol, name: symbol,
sectorsCount: 0, sectorsCount: 0,
symbol,
watchedByCount: 0 watchedByCount: 0
}; };
} }
); );
const marketData = await Promise.all(marketDataPromise); const assetProfiles = await Promise.all(assetProfilePromises);
return { marketData, count: marketData.length }; return { assetProfiles, count: assetProfiles.length };
}
private getExtendedPrismaClient() {
const symbolProfileExtension = Prisma.defineExtension((client) => {
return client.$extends({
result: {
symbolProfile: {
isUsedByUsersWithSubscription: {
compute: async ({ id }) => {
const { _count } =
await this.prismaService.symbolProfile.findUnique({
select: {
_count: {
select: {
activities: {
where: {
user: {
subscriptions: {
some: {
expiresAt: {
gt: new Date()
}
}
}
}
}
}
}
}
},
where: {
id
}
});
return _count.activities > 0;
}
}
}
}
});
});
return this.prismaService.$extends(symbolProfileExtension);
} }
} }

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

@ -10,11 +10,11 @@ import {
} from '@ghostfolio/common/helper'; } from '@ghostfolio/common/helper';
import { import {
AssetProfileIdentifier, AssetProfileIdentifier,
AssetProfileItem,
Filter, Filter,
InfoItem, InfoItem,
User User
} from '@ghostfolio/common/interfaces'; } from '@ghostfolio/common/interfaces';
import { AssetProfileItem } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { GfSymbolPipe } from '@ghostfolio/common/pipes'; import { GfSymbolPipe } from '@ghostfolio/common/pipes';
import { GfActivitiesFilterComponent } from '@ghostfolio/ui/activities-filter'; import { GfActivitiesFilterComponent } from '@ghostfolio/ui/activities-filter';
@ -384,15 +384,15 @@ export class GfAdminMarketDataComponent implements AfterViewInit, OnInit {
take: this.pageSize take: this.pageSize
}) })
.pipe(takeUntilDestroyed(this.destroyRef)) .pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(({ count, marketData }) => { .subscribe(({ assetProfiles, count }) => {
this.totalItems = count; this.totalItems = count;
this.dataSource = new MatTableDataSource( this.dataSource = new MatTableDataSource(
marketData.map((marketDataItem) => { assetProfiles.map((assetProfile) => {
return { return {
...marketDataItem, ...assetProfile,
isBenchmark: this.benchmarks.some(({ id }) => { isBenchmark: this.benchmarks.some(({ id }) => {
return id === marketDataItem.id; return id === assetProfile.id;
}) })
}; };
}) })

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

@ -7,10 +7,6 @@ import type { AdminMarketDataDetails } from './admin-market-data-details.interfa
import type { AdminUser } from './admin-user.interface'; import type { AdminUser } from './admin-user.interface';
import type { AssetClassSelectorOption } from './asset-class-selector-option.interface'; import type { AssetClassSelectorOption } from './asset-class-selector-option.interface';
import type { AssetProfileIdentifier } from './asset-profile-identifier.interface'; import type { AssetProfileIdentifier } from './asset-profile-identifier.interface';
import type {
AssetProfileItem,
AssetProfilesResponse
} from './asset-profiles-response.interface';
import type { BenchmarkProperty } from './benchmark-property.interface'; import type { BenchmarkProperty } from './benchmark-property.interface';
import type { Benchmark } from './benchmark.interface'; import type { Benchmark } from './benchmark.interface';
import type { Coupon } from './coupon.interface'; import type { Coupon } from './coupon.interface';
@ -46,6 +42,10 @@ import type { AdminUsersResponse } from './responses/admin-users-response.interf
import type { AiPromptResponse } from './responses/ai-prompt-response.interface'; import type { AiPromptResponse } from './responses/ai-prompt-response.interface';
import type { AiServiceHealthResponse } from './responses/ai-service-health-response.interface'; import type { AiServiceHealthResponse } from './responses/ai-service-health-response.interface';
import type { ApiKeyResponse } from './responses/api-key-response.interface'; import type { ApiKeyResponse } from './responses/api-key-response.interface';
import type {
AssetProfileItem,
AssetProfilesResponse
} from './responses/asset-profile-item.interface';
import type { AssetResponse } from './responses/asset-response.interface'; import type { AssetResponse } from './responses/asset-response.interface';
import type { BenchmarkMarketDataDetailsResponse } from './responses/benchmark-market-data-details-response.interface'; import type { BenchmarkMarketDataDetailsResponse } from './responses/benchmark-market-data-details-response.interface';
import type { BenchmarkResponse } from './responses/benchmark-response.interface'; import type { BenchmarkResponse } from './responses/benchmark-response.interface';

12
libs/common/src/lib/interfaces/asset-profiles-response.interface.ts → libs/common/src/lib/interfaces/responses/asset-profile-item.interface.ts

@ -1,10 +1,5 @@
import { AssetClass, AssetSubClass, DataSource } from '@prisma/client'; import { AssetClass, AssetSubClass, DataSource } from '@prisma/client';
export interface AssetProfilesResponse {
count: number;
marketData: AssetProfileItem[];
}
export interface AssetProfileItem { export interface AssetProfileItem {
activitiesCount: number; activitiesCount: number;
assetClass?: AssetClass; assetClass?: AssetClass;
@ -15,9 +10,9 @@ export interface AssetProfileItem {
dataSource: DataSource; dataSource: DataSource;
date: Date; date: Date;
id: string; id: string;
isin?: string | null;
isActive: boolean; isActive: boolean;
isBenchmark?: boolean; isBenchmark?: boolean;
isin?: string | null;
isUsedByUsersWithSubscription?: boolean; isUsedByUsersWithSubscription?: boolean;
lastMarketPrice: number; lastMarketPrice: number;
marketDataItemCount: number; marketDataItemCount: number;
@ -26,3 +21,8 @@ export interface AssetProfileItem {
symbol: string; symbol: string;
watchedByCount: number; watchedByCount: number;
} }
export interface AssetProfilesResponse {
assetProfiles: AssetProfileItem[];
count: number;
}

4
libs/ui/src/lib/assistant/assistant.component.ts

@ -688,8 +688,8 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit {
catchError(() => { catchError(() => {
return EMPTY; return EMPTY;
}), }),
map(({ marketData }) => { map(({ assetProfiles }) => {
return marketData.map( return assetProfiles.map(
({ assetSubClass, currency, dataSource, name, symbol }) => { ({ assetSubClass, currency, dataSource, name, symbol }) => {
return { return {
currency, currency,

Loading…
Cancel
Save