mirror of https://github.com/ghostfolio/ghostfolio
Browse Source
* Move market data management from admin to dedicated endpoint * Update changelogpull/4126/head^2
Thomas Kaul
1 month ago
committed by
GitHub
15 changed files with 260 additions and 45 deletions
@ -0,0 +1,136 @@ |
|||||
|
import { AdminService } from '@ghostfolio/api/app/admin/admin.service'; |
||||
|
import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service'; |
||||
|
import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service'; |
||||
|
import { MarketDataDetailsResponse } from '@ghostfolio/common/interfaces'; |
||||
|
import { hasPermission, permissions } from '@ghostfolio/common/permissions'; |
||||
|
import { RequestWithUser } from '@ghostfolio/common/types'; |
||||
|
|
||||
|
import { |
||||
|
Body, |
||||
|
Controller, |
||||
|
Get, |
||||
|
HttpException, |
||||
|
Inject, |
||||
|
Param, |
||||
|
Post, |
||||
|
UseGuards |
||||
|
} from '@nestjs/common'; |
||||
|
import { REQUEST } from '@nestjs/core'; |
||||
|
import { AuthGuard } from '@nestjs/passport'; |
||||
|
import { DataSource, Prisma } from '@prisma/client'; |
||||
|
import { parseISO } from 'date-fns'; |
||||
|
import { getReasonPhrase, StatusCodes } from 'http-status-codes'; |
||||
|
|
||||
|
import { UpdateBulkMarketDataDto } from './update-bulk-market-data.dto'; |
||||
|
|
||||
|
@Controller('market-data') |
||||
|
export class MarketDataController { |
||||
|
public constructor( |
||||
|
private readonly adminService: AdminService, |
||||
|
private readonly marketDataService: MarketDataService, |
||||
|
@Inject(REQUEST) private readonly request: RequestWithUser, |
||||
|
private readonly symbolProfileService: SymbolProfileService |
||||
|
) {} |
||||
|
|
||||
|
@Get(':dataSource/:symbol') |
||||
|
@UseGuards(AuthGuard('jwt')) |
||||
|
public async getMarketDataBySymbol( |
||||
|
@Param('dataSource') dataSource: DataSource, |
||||
|
@Param('symbol') symbol: string |
||||
|
): Promise<MarketDataDetailsResponse> { |
||||
|
const [assetProfile] = await this.symbolProfileService.getSymbolProfiles([ |
||||
|
{ dataSource, symbol } |
||||
|
]); |
||||
|
|
||||
|
if (!assetProfile) { |
||||
|
throw new HttpException( |
||||
|
getReasonPhrase(StatusCodes.NOT_FOUND), |
||||
|
StatusCodes.NOT_FOUND |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
const canReadAllAssetProfiles = hasPermission( |
||||
|
this.request.user.permissions, |
||||
|
permissions.readMarketData |
||||
|
); |
||||
|
|
||||
|
const canReadOwnAssetProfile = |
||||
|
assetProfile.userId === this.request.user.id && |
||||
|
hasPermission( |
||||
|
this.request.user.permissions, |
||||
|
permissions.readMarketDataOfOwnAssetProfile |
||||
|
); |
||||
|
|
||||
|
if (!canReadAllAssetProfiles && !canReadOwnAssetProfile) { |
||||
|
throw new HttpException( |
||||
|
assetProfile.userId |
||||
|
? getReasonPhrase(StatusCodes.NOT_FOUND) |
||||
|
: getReasonPhrase(StatusCodes.FORBIDDEN), |
||||
|
assetProfile.userId ? StatusCodes.NOT_FOUND : StatusCodes.FORBIDDEN |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
return this.adminService.getMarketDataBySymbol({ dataSource, symbol }); |
||||
|
} |
||||
|
|
||||
|
@Post(':dataSource/:symbol') |
||||
|
@UseGuards(AuthGuard('jwt')) |
||||
|
public async updateMarketData( |
||||
|
@Body() data: UpdateBulkMarketDataDto, |
||||
|
@Param('dataSource') dataSource: DataSource, |
||||
|
@Param('symbol') symbol: string |
||||
|
) { |
||||
|
const [assetProfile] = await this.symbolProfileService.getSymbolProfiles([ |
||||
|
{ dataSource, symbol } |
||||
|
]); |
||||
|
|
||||
|
if (!assetProfile) { |
||||
|
throw new HttpException( |
||||
|
getReasonPhrase(StatusCodes.NOT_FOUND), |
||||
|
StatusCodes.NOT_FOUND |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
const canUpsertAllAssetProfiles = |
||||
|
hasPermission( |
||||
|
this.request.user.permissions, |
||||
|
permissions.createMarketData |
||||
|
) && |
||||
|
hasPermission( |
||||
|
this.request.user.permissions, |
||||
|
permissions.updateMarketData |
||||
|
); |
||||
|
|
||||
|
const canUpsertOwnAssetProfile = |
||||
|
assetProfile.userId === this.request.user.id && |
||||
|
hasPermission( |
||||
|
this.request.user.permissions, |
||||
|
permissions.createMarketDataOfOwnAssetProfile |
||||
|
) && |
||||
|
hasPermission( |
||||
|
this.request.user.permissions, |
||||
|
permissions.updateMarketDataOfOwnAssetProfile |
||||
|
); |
||||
|
|
||||
|
if (!canUpsertAllAssetProfiles && !canUpsertOwnAssetProfile) { |
||||
|
throw new HttpException( |
||||
|
getReasonPhrase(StatusCodes.FORBIDDEN), |
||||
|
StatusCodes.FORBIDDEN |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
const dataBulkUpdate: Prisma.MarketDataUpdateInput[] = data.marketData.map( |
||||
|
({ date, marketPrice }) => ({ |
||||
|
dataSource, |
||||
|
marketPrice, |
||||
|
symbol, |
||||
|
date: parseISO(date), |
||||
|
state: 'CLOSE' |
||||
|
}) |
||||
|
); |
||||
|
|
||||
|
return this.marketDataService.updateMany({ |
||||
|
data: dataBulkUpdate |
||||
|
}); |
||||
|
} |
||||
|
} |
@ -0,0 +1,13 @@ |
|||||
|
import { AdminModule } from '@ghostfolio/api/app/admin/admin.module'; |
||||
|
import { MarketDataModule as MarketDataServiceModule } from '@ghostfolio/api/services/market-data/market-data.module'; |
||||
|
import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module'; |
||||
|
|
||||
|
import { Module } from '@nestjs/common'; |
||||
|
|
||||
|
import { MarketDataController } from './market-data.controller'; |
||||
|
|
||||
|
@Module({ |
||||
|
controllers: [MarketDataController], |
||||
|
imports: [AdminModule, MarketDataServiceModule, SymbolProfileModule] |
||||
|
}) |
||||
|
export class MarketDataModule {} |
@ -0,0 +1,24 @@ |
|||||
|
import { Type } from 'class-transformer'; |
||||
|
import { |
||||
|
ArrayNotEmpty, |
||||
|
IsArray, |
||||
|
IsISO8601, |
||||
|
IsNumber, |
||||
|
IsOptional |
||||
|
} from 'class-validator'; |
||||
|
|
||||
|
export class UpdateBulkMarketDataDto { |
||||
|
@ArrayNotEmpty() |
||||
|
@IsArray() |
||||
|
@Type(() => UpdateMarketDataDto) |
||||
|
marketData: UpdateMarketDataDto[]; |
||||
|
} |
||||
|
|
||||
|
class UpdateMarketDataDto { |
||||
|
@IsISO8601() |
||||
|
@IsOptional() |
||||
|
date?: string; |
||||
|
|
||||
|
@IsNumber() |
||||
|
marketPrice: number; |
||||
|
} |
@ -0,0 +1,8 @@ |
|||||
|
import { MarketData } from '@prisma/client'; |
||||
|
|
||||
|
import { EnhancedSymbolProfile } from '../enhanced-symbol-profile.interface'; |
||||
|
|
||||
|
export interface MarketDataDetailsResponse { |
||||
|
assetProfile: Partial<EnhancedSymbolProfile>; |
||||
|
marketData: MarketData[]; |
||||
|
} |
Loading…
Reference in new issue