Feature/introduce isUsedByUsersWithSubscription flag (#3573)
This commit is contained in:
parent
d5c56fb16c
commit
43afb16808
@ -27,12 +27,13 @@ import {
|
||||
} from '@ghostfolio/common/interfaces';
|
||||
import { MarketDataPreset } from '@ghostfolio/common/types';
|
||||
|
||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
import { BadRequestException, Injectable, Logger } from '@nestjs/common';
|
||||
import {
|
||||
AssetClass,
|
||||
AssetSubClass,
|
||||
DataSource,
|
||||
Prisma,
|
||||
PrismaClient,
|
||||
Property,
|
||||
SymbolProfile
|
||||
} from '@prisma/client';
|
||||
@ -212,98 +213,113 @@ export class AdminService {
|
||||
}
|
||||
}
|
||||
|
||||
let [assetProfiles, count] = await Promise.all([
|
||||
this.prismaService.symbolProfile.findMany({
|
||||
orderBy,
|
||||
skip,
|
||||
take,
|
||||
where,
|
||||
select: {
|
||||
_count: {
|
||||
select: { Order: true }
|
||||
},
|
||||
assetClass: true,
|
||||
assetSubClass: true,
|
||||
comment: true,
|
||||
countries: true,
|
||||
currency: true,
|
||||
dataSource: true,
|
||||
id: true,
|
||||
name: true,
|
||||
Order: {
|
||||
orderBy: [{ date: 'asc' }],
|
||||
select: { date: true },
|
||||
take: 1
|
||||
},
|
||||
scraperConfiguration: true,
|
||||
sectors: true,
|
||||
symbol: true
|
||||
const extendedPrismaClient = this.getExtendedPrismaClient();
|
||||
|
||||
try {
|
||||
let [assetProfiles, count] = await Promise.all([
|
||||
extendedPrismaClient.symbolProfile.findMany({
|
||||
orderBy,
|
||||
skip,
|
||||
take,
|
||||
where,
|
||||
select: {
|
||||
_count: {
|
||||
select: { Order: true }
|
||||
},
|
||||
assetClass: true,
|
||||
assetSubClass: true,
|
||||
comment: true,
|
||||
countries: true,
|
||||
currency: true,
|
||||
dataSource: true,
|
||||
id: true,
|
||||
isUsedByUsersWithSubscription: true,
|
||||
name: true,
|
||||
Order: {
|
||||
orderBy: [{ date: 'asc' }],
|
||||
select: { date: true },
|
||||
take: 1
|
||||
},
|
||||
scraperConfiguration: true,
|
||||
sectors: true,
|
||||
symbol: true
|
||||
}
|
||||
}),
|
||||
this.prismaService.symbolProfile.count({ where })
|
||||
]);
|
||||
|
||||
let marketData: AdminMarketDataItem[] = await Promise.all(
|
||||
assetProfiles.map(
|
||||
async ({
|
||||
_count,
|
||||
assetClass,
|
||||
assetSubClass,
|
||||
comment,
|
||||
countries,
|
||||
currency,
|
||||
dataSource,
|
||||
id,
|
||||
isUsedByUsersWithSubscription,
|
||||
name,
|
||||
Order,
|
||||
sectors,
|
||||
symbol
|
||||
}) => {
|
||||
const countriesCount = countries
|
||||
? Object.keys(countries).length
|
||||
: 0;
|
||||
const marketDataItemCount =
|
||||
marketDataItems.find((marketDataItem) => {
|
||||
return (
|
||||
marketDataItem.dataSource === dataSource &&
|
||||
marketDataItem.symbol === symbol
|
||||
);
|
||||
})?._count ?? 0;
|
||||
const sectorsCount = sectors ? Object.keys(sectors).length : 0;
|
||||
|
||||
return {
|
||||
assetClass,
|
||||
assetSubClass,
|
||||
comment,
|
||||
currency,
|
||||
countriesCount,
|
||||
dataSource,
|
||||
id,
|
||||
name,
|
||||
symbol,
|
||||
marketDataItemCount,
|
||||
sectorsCount,
|
||||
activitiesCount: _count.Order,
|
||||
date: Order?.[0]?.date,
|
||||
isUsedByUsersWithSubscription: await isUsedByUsersWithSubscription
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
if (presetId) {
|
||||
if (presetId === 'ETF_WITHOUT_COUNTRIES') {
|
||||
marketData = marketData.filter(({ countriesCount }) => {
|
||||
return countriesCount === 0;
|
||||
});
|
||||
} else if (presetId === 'ETF_WITHOUT_SECTORS') {
|
||||
marketData = marketData.filter(({ sectorsCount }) => {
|
||||
return sectorsCount === 0;
|
||||
});
|
||||
}
|
||||
}),
|
||||
this.prismaService.symbolProfile.count({ where })
|
||||
]);
|
||||
|
||||
let marketData: AdminMarketDataItem[] = assetProfiles.map(
|
||||
({
|
||||
_count,
|
||||
assetClass,
|
||||
assetSubClass,
|
||||
comment,
|
||||
countries,
|
||||
currency,
|
||||
dataSource,
|
||||
id,
|
||||
name,
|
||||
Order,
|
||||
sectors,
|
||||
symbol
|
||||
}) => {
|
||||
const countriesCount = countries ? Object.keys(countries).length : 0;
|
||||
const marketDataItemCount =
|
||||
marketDataItems.find((marketDataItem) => {
|
||||
return (
|
||||
marketDataItem.dataSource === dataSource &&
|
||||
marketDataItem.symbol === symbol
|
||||
);
|
||||
})?._count ?? 0;
|
||||
const sectorsCount = sectors ? Object.keys(sectors).length : 0;
|
||||
|
||||
return {
|
||||
assetClass,
|
||||
assetSubClass,
|
||||
comment,
|
||||
currency,
|
||||
countriesCount,
|
||||
dataSource,
|
||||
id,
|
||||
name,
|
||||
symbol,
|
||||
marketDataItemCount,
|
||||
sectorsCount,
|
||||
activitiesCount: _count.Order,
|
||||
date: Order?.[0]?.date
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
if (presetId) {
|
||||
if (presetId === 'ETF_WITHOUT_COUNTRIES') {
|
||||
marketData = marketData.filter(({ countriesCount }) => {
|
||||
return countriesCount === 0;
|
||||
});
|
||||
} else if (presetId === 'ETF_WITHOUT_SECTORS') {
|
||||
marketData = marketData.filter(({ sectorsCount }) => {
|
||||
return sectorsCount === 0;
|
||||
});
|
||||
count = marketData.length;
|
||||
}
|
||||
|
||||
count = marketData.length;
|
||||
return {
|
||||
count,
|
||||
marketData
|
||||
};
|
||||
} finally {
|
||||
await extendedPrismaClient.$disconnect();
|
||||
|
||||
Logger.debug('Disconnect extended prisma client', 'AdminService');
|
||||
}
|
||||
|
||||
return {
|
||||
count,
|
||||
marketData
|
||||
};
|
||||
}
|
||||
|
||||
public async getMarketDataBySymbol({
|
||||
@ -431,6 +447,52 @@ export class AdminService {
|
||||
return response;
|
||||
}
|
||||
|
||||
private getExtendedPrismaClient() {
|
||||
Logger.debug('Connect extended prisma client', 'AdminService');
|
||||
|
||||
const symbolProfileExtension = Prisma.defineExtension((client) => {
|
||||
return client.$extends({
|
||||
result: {
|
||||
symbolProfile: {
|
||||
isUsedByUsersWithSubscription: {
|
||||
compute: async ({ id }) => {
|
||||
const { _count } =
|
||||
await this.prismaService.symbolProfile.findUnique({
|
||||
select: {
|
||||
_count: {
|
||||
select: {
|
||||
Order: {
|
||||
where: {
|
||||
User: {
|
||||
Subscription: {
|
||||
some: {
|
||||
expiresAt: {
|
||||
gt: new Date()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
where: {
|
||||
id
|
||||
}
|
||||
});
|
||||
|
||||
return _count.Order > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return new PrismaClient().$extends(symbolProfileExtension);
|
||||
}
|
||||
|
||||
private async getMarketDataForCurrencies(): Promise<AdminMarketData> {
|
||||
const marketDataItems = await this.prismaService.marketData.groupBy({
|
||||
_count: true,
|
||||
|
@ -6,8 +6,14 @@ import {
|
||||
ghostfolioScraperApiSymbolPrefix
|
||||
} from '@ghostfolio/common/config';
|
||||
import { getDateFormatString } from '@ghostfolio/common/helper';
|
||||
import { Filter, UniqueAsset, User } from '@ghostfolio/common/interfaces';
|
||||
import {
|
||||
Filter,
|
||||
InfoItem,
|
||||
UniqueAsset,
|
||||
User
|
||||
} from '@ghostfolio/common/interfaces';
|
||||
import { AdminMarketDataItem } from '@ghostfolio/common/interfaces/admin-market-data.interface';
|
||||
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||
import { translate } from '@ghostfolio/ui/i18n';
|
||||
|
||||
import { SelectionModel } from '@angular/cdk/collections';
|
||||
@ -97,22 +103,11 @@ export class AdminMarketDataComponent
|
||||
new MatTableDataSource();
|
||||
public defaultDateFormat: string;
|
||||
public deviceType: string;
|
||||
public displayedColumns = [
|
||||
'select',
|
||||
'nameWithSymbol',
|
||||
'dataSource',
|
||||
'assetClass',
|
||||
'assetSubClass',
|
||||
'date',
|
||||
'activitiesCount',
|
||||
'marketDataItemCount',
|
||||
'sectorsCount',
|
||||
'countriesCount',
|
||||
'comment',
|
||||
'actions'
|
||||
];
|
||||
public displayedColumns: string[] = [];
|
||||
public filters$ = new Subject<Filter[]>();
|
||||
public ghostfolioScraperApiSymbolPrefix = ghostfolioScraperApiSymbolPrefix;
|
||||
public hasPermissionForSubscription: boolean;
|
||||
public info: InfoItem;
|
||||
public isLoading = false;
|
||||
public isUUID = isUUID;
|
||||
public placeholder = '';
|
||||
@ -134,6 +129,33 @@ export class AdminMarketDataComponent
|
||||
private router: Router,
|
||||
private userService: UserService
|
||||
) {
|
||||
this.info = this.dataService.fetchInfo();
|
||||
|
||||
this.hasPermissionForSubscription = hasPermission(
|
||||
this.info?.globalPermissions,
|
||||
permissions.enableSubscription
|
||||
);
|
||||
|
||||
this.displayedColumns = [
|
||||
'select',
|
||||
'nameWithSymbol',
|
||||
'dataSource',
|
||||
'assetClass',
|
||||
'assetSubClass',
|
||||
'date',
|
||||
'activitiesCount',
|
||||
'marketDataItemCount',
|
||||
'sectorsCount',
|
||||
'countriesCount'
|
||||
];
|
||||
|
||||
if (this.hasPermissionForSubscription) {
|
||||
this.displayedColumns.push('isUsedByUsersWithSubscription');
|
||||
}
|
||||
|
||||
this.displayedColumns.push('comment');
|
||||
this.displayedColumns.push('actions');
|
||||
|
||||
this.route.queryParams
|
||||
.pipe(takeUntil(this.unsubscribeSubject))
|
||||
.subscribe((params) => {
|
||||
|
@ -144,6 +144,15 @@
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="isUsedByUsersWithSubscription">
|
||||
<th *matHeaderCellDef class="px-1" mat-header-cell></th>
|
||||
<td *matCellDef="let element" class="px-1" mat-cell>
|
||||
@if (element.isUsedByUsersWithSubscription) {
|
||||
<gf-premium-indicator [enableLink]="false" />
|
||||
}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="comment">
|
||||
<th *matHeaderCellDef class="px-1" mat-header-cell></th>
|
||||
<td *matCellDef="let element" class="px-1" mat-cell>
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module';
|
||||
import { GfActivitiesFilterComponent } from '@ghostfolio/ui/activities-filter';
|
||||
import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator';
|
||||
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||
@ -24,6 +25,7 @@ import { GfCreateAssetProfileDialogModule } from './create-asset-profile-dialog/
|
||||
GfActivitiesFilterComponent,
|
||||
GfAssetProfileDialogModule,
|
||||
GfCreateAssetProfileDialogModule,
|
||||
GfPremiumIndicatorComponent,
|
||||
GfSymbolModule,
|
||||
MatButtonModule,
|
||||
MatCheckboxModule,
|
||||
|
@ -15,6 +15,7 @@ export interface AdminMarketDataItem {
|
||||
date: Date;
|
||||
id: string;
|
||||
isBenchmark?: boolean;
|
||||
isUsedByUsersWithSubscription?: boolean;
|
||||
marketDataItemCount: number;
|
||||
name: string;
|
||||
sectorsCount: number;
|
||||
|
Loading…
x
Reference in New Issue
Block a user