Feature/support deleting symbol profile data (#669)
* Add support for deleting symbol profile data * Update changelog
This commit is contained in:
parent
5d8bde5a70
commit
dc424a86ec
@ -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) {
|
||||
this.marketDataDetails = [];
|
||||
|
||||
if (this.currentSymbol === aSymbol) {
|
||||
this.currentSymbol = '';
|
||||
} else {
|
||||
this.currentSymbol = aSymbol;
|
||||
|
||||
this.fetchAdminMarketDataBySymbol(this.currentSymbol);
|
||||
}
|
||||
}
|
||||
|
||||
public onMarketDataChanged(withRefresh: boolean = false) {
|
||||
if (withRefresh) {
|
||||
this.fetchAdminMarketData();
|
||||
this.fetchAdminMarketDataBySymbol(this.currentSymbol);
|
||||
this.fetchAdminMarketDataBySymbol({
|
||||
dataSource: this.currentDataSource,
|
||||
symbol: this.currentSymbol
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public setCurrentProfile({
|
||||
dataSource,
|
||||
symbol
|
||||
}: {
|
||||
dataSource: DataSource;
|
||||
symbol: string;
|
||||
}) {
|
||||
this.marketDataDetails = [];
|
||||
|
||||
if (this.currentSymbol === symbol) {
|
||||
this.currentDataSource = undefined;
|
||||
this.currentSymbol = '';
|
||||
} else {
|
||||
this.currentDataSource = dataSource;
|
||||
this.currentSymbol = symbol;
|
||||
|
||||
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…
x
Reference in New Issue
Block a user