Feature/support deleting symbol profile data (#669)

* Add support for deleting symbol profile data

* Update changelog
pull/670/head
Thomas Kaul 3 years ago committed by GitHub
parent 5d8bde5a70
commit dc424a86ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
### Added
- Added support for deleting symbol profile data in the admin control panel
### Changed
- Used `dataSource` and `symbol` from `SymbolProfile` instead of the `order` object (in `ExportService` and `PortfolioService`)

@ -11,6 +11,7 @@ import type { RequestWithUser } from '@ghostfolio/common/types';
import {
Body,
Controller,
Delete,
Get,
HttpException,
Inject,
@ -195,9 +196,10 @@ export class AdminController {
return this.adminService.getMarketData();
}
@Get('market-data/:symbol')
@Get('market-data/:dataSource/:symbol')
@UseGuards(AuthGuard('jwt'))
public async getMarketDataBySymbol(
@Param('dataSource') dataSource: DataSource,
@Param('symbol') symbol: string
): Promise<AdminMarketDataDetails> {
if (
@ -212,7 +214,7 @@ export class AdminController {
);
}
return this.adminService.getMarketDataBySymbol(symbol);
return this.adminService.getMarketDataBySymbol({ dataSource, symbol });
}
@Put('market-data/:dataSource/:symbol/:dateString')
@ -248,6 +250,27 @@ export class AdminController {
});
}
@Delete('profile-data/:dataSource/:symbol')
@UseGuards(AuthGuard('jwt'))
public async deleteProfileData(
@Param('dataSource') dataSource: DataSource,
@Param('symbol') symbol: string
): Promise<void> {
if (
!hasPermission(
this.request.user.permissions,
permissions.accessAdminControl
)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
return this.adminService.deleteProfileData({ dataSource, symbol });
}
@Put('settings/:key')
@UseGuards(AuthGuard('jwt'))
public async updateProperty(

@ -6,6 +6,7 @@ import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-d
import { MarketDataModule } from '@ghostfolio/api/services/market-data.module';
import { PrismaModule } from '@ghostfolio/api/services/prisma.module';
import { PropertyModule } from '@ghostfolio/api/services/property/property.module';
import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile.module';
import { Module } from '@nestjs/common';
import { AdminController } from './admin.controller';
@ -20,7 +21,8 @@ import { AdminService } from './admin.service';
MarketDataModule,
PrismaModule,
PropertyModule,
SubscriptionModule
SubscriptionModule,
SymbolProfileModule
],
controllers: [AdminController],
providers: [AdminService],

@ -5,6 +5,7 @@ import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-
import { MarketDataService } from '@ghostfolio/api/services/market-data.service';
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
import { PropertyService } from '@ghostfolio/api/services/property/property.service';
import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile.service';
import { PROPERTY_CURRENCIES, baseCurrency } from '@ghostfolio/common/config';
import {
AdminData,
@ -13,7 +14,7 @@ import {
AdminMarketDataItem
} from '@ghostfolio/common/interfaces';
import { Injectable } from '@nestjs/common';
import { Property } from '@prisma/client';
import { DataSource, Property } from '@prisma/client';
import { differenceInDays } from 'date-fns';
@Injectable()
@ -25,9 +26,21 @@ export class AdminService {
private readonly marketDataService: MarketDataService,
private readonly prismaService: PrismaService,
private readonly propertyService: PropertyService,
private readonly subscriptionService: SubscriptionService
private readonly subscriptionService: SubscriptionService,
private readonly symbolProfileService: SymbolProfileService
) {}
public async deleteProfileData({
dataSource,
symbol
}: {
dataSource: DataSource;
symbol: string;
}) {
await this.marketDataService.deleteMany({ dataSource, symbol });
await this.symbolProfileService.delete({ dataSource, symbol });
}
public async get(): Promise<AdminData> {
return {
dataGatheringProgress:
@ -121,16 +134,21 @@ export class AdminService {
};
}
public async getMarketDataBySymbol(
aSymbol: string
): Promise<AdminMarketDataDetails> {
public async getMarketDataBySymbol({
dataSource,
symbol
}: {
dataSource: DataSource;
symbol: string;
}): Promise<AdminMarketDataDetails> {
return {
marketData: await this.marketDataService.marketDataItems({
orderBy: {
date: 'asc'
},
where: {
symbol: aSymbol
dataSource,
symbol
}
})
};

@ -9,6 +9,21 @@ import { DataSource, MarketData, Prisma } from '@prisma/client';
export class MarketDataService {
public constructor(private readonly prismaService: PrismaService) {}
public async deleteMany({
dataSource,
symbol
}: {
dataSource: DataSource;
symbol: string;
}) {
return this.prismaService.marketData.deleteMany({
where: {
dataSource,
symbol
}
});
}
public async get({
date,
symbol

@ -4,14 +4,26 @@ import { UNKNOWN_KEY } from '@ghostfolio/common/config';
import { Country } from '@ghostfolio/common/interfaces/country.interface';
import { Sector } from '@ghostfolio/common/interfaces/sector.interface';
import { Injectable } from '@nestjs/common';
import { Prisma, SymbolProfile } from '@prisma/client';
import { DataSource, Prisma, SymbolProfile } from '@prisma/client';
import { continents, countries } from 'countries-list';
import { ScraperConfiguration } from './data-provider/ghostfolio-scraper-api/interfaces/scraper-configuration.interface';
@Injectable()
export class SymbolProfileService {
constructor(private readonly prismaService: PrismaService) {}
public constructor(private readonly prismaService: PrismaService) {}
public async delete({
dataSource,
symbol
}: {
dataSource: DataSource;
symbol: string;
}) {
return this.prismaService.symbolProfile.delete({
where: { dataSource_symbol: { dataSource, symbol } }
});
}
public async getSymbolProfiles(
symbols: string[]

@ -20,6 +20,7 @@ import { takeUntil } from 'rxjs/operators';
templateUrl: './admin-market-data.html'
})
export class AdminMarketDataComponent implements OnDestroy, OnInit {
public currentDataSource: DataSource;
public currentSymbol: string;
public defaultDateFormat = DEFAULT_DATE_FORMAT;
public marketData: AdminMarketDataItem[] = [];
@ -43,6 +44,19 @@ export class AdminMarketDataComponent implements OnDestroy, OnInit {
this.fetchAdminMarketData();
}
public onDeleteProfileData({
dataSource,
symbol
}: {
dataSource: DataSource;
symbol: string;
}) {
this.adminService
.deleteProfileData({ dataSource, symbol })
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(() => {});
}
public onGatherProfileDataBySymbol({
dataSource,
symbol
@ -69,22 +83,33 @@ export class AdminMarketDataComponent implements OnDestroy, OnInit {
.subscribe(() => {});
}
public setCurrentSymbol(aSymbol: string) {
public onMarketDataChanged(withRefresh: boolean = false) {
if (withRefresh) {
this.fetchAdminMarketData();
this.fetchAdminMarketDataBySymbol({
dataSource: this.currentDataSource,
symbol: this.currentSymbol
});
}
}
public setCurrentProfile({
dataSource,
symbol
}: {
dataSource: DataSource;
symbol: string;
}) {
this.marketDataDetails = [];
if (this.currentSymbol === aSymbol) {
if (this.currentSymbol === symbol) {
this.currentDataSource = undefined;
this.currentSymbol = '';
} else {
this.currentSymbol = aSymbol;
this.currentDataSource = dataSource;
this.currentSymbol = symbol;
this.fetchAdminMarketDataBySymbol(this.currentSymbol);
}
}
public onMarketDataChanged(withRefresh: boolean = false) {
if (withRefresh) {
this.fetchAdminMarketData();
this.fetchAdminMarketDataBySymbol(this.currentSymbol);
this.fetchAdminMarketDataBySymbol({ dataSource, symbol });
}
}
@ -104,9 +129,15 @@ export class AdminMarketDataComponent implements OnDestroy, OnInit {
});
}
private fetchAdminMarketDataBySymbol(aSymbol: string) {
this.dataService
.fetchAdminMarketDataBySymbol(aSymbol)
private fetchAdminMarketDataBySymbol({
dataSource,
symbol
}: {
dataSource: DataSource;
symbol: string;
}) {
this.adminService
.fetchAdminMarketDataBySymbol({ dataSource, symbol })
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ marketData }) => {
this.marketDataDetails = marketData;

@ -16,7 +16,7 @@
<ng-container *ngFor="let item of marketData; let i = index">
<tr
class="cursor-pointer mat-row"
(click)="setCurrentSymbol(item.symbol)"
(click)="setCurrentProfile({ dataSource: item.dataSource, symbol: item.symbol })"
>
<td class="mat-cell px-1 py-2">{{ item.symbol }}</td>
<td class="mat-cell px-1 py-2">{{ item.dataSource }}</td>
@ -49,11 +49,19 @@
>
Gather Profile Data
</button>
<button
i18n
mat-menu-item
[disabled]="item.activityCount !== 0"
(click)="onDeleteProfileData({dataSource: item.dataSource, symbol: item.symbol})"
>
Delete Profile Data
</button>
</mat-menu>
</td>
</tr>
<tr *ngIf="currentSymbol === item.symbol" class="mat-row">
<td class="p-1" colspan="4">
<td class="p-1" colspan="6">
<gf-admin-market-data-detail
[dataSource]="item.dataSource"
[marketData]="marketDataDetails"

@ -3,8 +3,10 @@ import { Injectable } from '@angular/core';
import { UpdateMarketDataDto } from '@ghostfolio/api/app/admin/update-market-data.dto';
import { IDataProviderHistoricalResponse } from '@ghostfolio/api/services/interfaces/interfaces';
import { DATE_FORMAT } from '@ghostfolio/common/helper';
import { AdminMarketDataDetails } from '@ghostfolio/common/interfaces';
import { DataSource, MarketData } from '@prisma/client';
import { format } from 'date-fns';
import { format, parseISO } from 'date-fns';
import { map, Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
@ -12,6 +14,37 @@ import { format } from 'date-fns';
export class AdminService {
public constructor(private http: HttpClient) {}
public deleteProfileData({
dataSource,
symbol
}: {
dataSource: DataSource;
symbol: string;
}) {
return this.http.delete<void>(
`/api/admin/profile-data/${dataSource}/${symbol}`
);
}
public fetchAdminMarketDataBySymbol({
dataSource,
symbol
}: {
dataSource: DataSource;
symbol: string;
}): Observable<AdminMarketDataDetails> {
return this.http
.get<any>(`/api/admin/market-data/${dataSource}/${symbol}`)
.pipe(
map((data) => {
for (const item of data.marketData) {
item.date = parseISO(item.date);
}
return data;
})
);
}
public gatherMax() {
return this.http.post<void>(`/api/admin/gather/max`, {});
}

@ -18,7 +18,6 @@ import {
Accounts,
AdminData,
AdminMarketData,
AdminMarketDataDetails,
Export,
InfoItem,
PortfolioChart,
@ -69,19 +68,6 @@ export class DataService {
return this.http.get<AdminMarketData>('/api/admin/market-data');
}
public fetchAdminMarketDataBySymbol(
aSymbol: string
): Observable<AdminMarketDataDetails> {
return this.http.get<any>(`/api/admin/market-data/${aSymbol}`).pipe(
map((data) => {
for (const item of data.marketData) {
item.date = parseISO(item.date);
}
return data;
})
);
}
public deleteAccess(aId: string) {
return this.http.delete<any>(`/api/access/${aId}`);
}

Loading…
Cancel
Save