mirror of https://github.com/ghostfolio/ghostfolio
Browse Source
* Extend public API with endpoint to update asset profile data * Update changelog --------- Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>pull/7016/head
committed by
GitHub
11 changed files with 262 additions and 12 deletions
@ -0,0 +1,51 @@ |
|||
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator'; |
|||
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard'; |
|||
import { UpdateAssetProfileDataDto } from '@ghostfolio/common/dtos'; |
|||
import { EnhancedSymbolProfile } from '@ghostfolio/common/interfaces'; |
|||
import { permissions } from '@ghostfolio/common/permissions'; |
|||
import { RequestWithUser } from '@ghostfolio/common/types'; |
|||
|
|||
import { |
|||
Body, |
|||
Controller, |
|||
HttpException, |
|||
Inject, |
|||
Param, |
|||
Patch, |
|||
UseGuards |
|||
} from '@nestjs/common'; |
|||
import { REQUEST } from '@nestjs/core'; |
|||
import { AuthGuard } from '@nestjs/passport'; |
|||
import { DataSource } from '@prisma/client'; |
|||
import { StatusCodes, getReasonPhrase } from 'http-status-codes'; |
|||
|
|||
import { AssetProfilesService } from './asset-profiles.service'; |
|||
|
|||
@Controller('asset-profiles') |
|||
export class AssetProfilesController { |
|||
public constructor( |
|||
private readonly assetProfilesService: AssetProfilesService, |
|||
@Inject(REQUEST) private readonly request: RequestWithUser |
|||
) {} |
|||
|
|||
@HasPermission(permissions.accessAdminControl) |
|||
@Patch(':dataSource/:symbol') |
|||
@UseGuards(AuthGuard('jwt'), HasPermissionGuard) |
|||
public async updateAssetProfileData( |
|||
@Body() assetProfileData: UpdateAssetProfileDataDto, |
|||
@Param('dataSource') dataSource: DataSource, |
|||
@Param('symbol') symbol: string |
|||
): Promise<EnhancedSymbolProfile> { |
|||
if (!this.request.user.settings.settings.isExperimentalFeatures) { |
|||
throw new HttpException( |
|||
getReasonPhrase(StatusCodes.NOT_FOUND), |
|||
StatusCodes.NOT_FOUND |
|||
); |
|||
} |
|||
|
|||
return this.assetProfilesService.updateAssetProfileData( |
|||
{ dataSource, symbol }, |
|||
assetProfileData |
|||
); |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module'; |
|||
|
|||
import { Module } from '@nestjs/common'; |
|||
|
|||
import { AssetProfilesController } from './asset-profiles.controller'; |
|||
import { AssetProfilesService } from './asset-profiles.service'; |
|||
|
|||
@Module({ |
|||
controllers: [AssetProfilesController], |
|||
imports: [SymbolProfileModule], |
|||
providers: [AssetProfilesService] |
|||
}) |
|||
export class AssetProfilesModule {} |
|||
@ -0,0 +1,90 @@ |
|||
import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service'; |
|||
import { UpdateAssetProfileDataDto } from '@ghostfolio/common/dtos'; |
|||
import { |
|||
AssetProfileIdentifier, |
|||
EnhancedSymbolProfile |
|||
} from '@ghostfolio/common/interfaces'; |
|||
|
|||
import { Injectable, NotFoundException } from '@nestjs/common'; |
|||
import { Prisma } from '@prisma/client'; |
|||
|
|||
@Injectable() |
|||
export class AssetProfilesService { |
|||
public constructor( |
|||
private readonly symbolProfileService: SymbolProfileService |
|||
) {} |
|||
|
|||
public async updateAssetProfileData( |
|||
{ dataSource, symbol }: AssetProfileIdentifier, |
|||
assetProfileData: UpdateAssetProfileDataDto |
|||
): Promise<EnhancedSymbolProfile> { |
|||
const notFoundMessage = `Could not find the asset profile for ${symbol} (${dataSource})`; |
|||
|
|||
const data = this.getAssetProfileDataUpdate(assetProfileData); |
|||
|
|||
if (Object.keys(data).length > 0) { |
|||
try { |
|||
await this.symbolProfileService.updateSymbolProfile( |
|||
{ |
|||
dataSource, |
|||
symbol |
|||
}, |
|||
this.symbolProfileService.getAssetProfileUpdateInput( |
|||
{ dataSource, symbol }, |
|||
data |
|||
) |
|||
); |
|||
} catch (error) { |
|||
if ( |
|||
error instanceof Prisma.PrismaClientKnownRequestError && |
|||
error.code === 'P2025' |
|||
) { |
|||
throw new NotFoundException(notFoundMessage); |
|||
} |
|||
|
|||
throw error; |
|||
} |
|||
} |
|||
|
|||
const [assetProfile] = await this.symbolProfileService.getSymbolProfiles([ |
|||
{ |
|||
dataSource, |
|||
symbol |
|||
} |
|||
]); |
|||
|
|||
if (!assetProfile) { |
|||
throw new NotFoundException(notFoundMessage); |
|||
} |
|||
|
|||
return assetProfile; |
|||
} |
|||
|
|||
private getAssetProfileDataUpdate({ |
|||
countries, |
|||
holdings, |
|||
sectors |
|||
}: UpdateAssetProfileDataDto): Pick< |
|||
Prisma.SymbolProfileUpdateInput, |
|||
'countries' | 'holdings' | 'sectors' |
|||
> { |
|||
const data: Pick< |
|||
Prisma.SymbolProfileUpdateInput, |
|||
'countries' | 'holdings' | 'sectors' |
|||
> = {}; |
|||
|
|||
if (countries !== undefined) { |
|||
data.countries = countries as Prisma.JsonArray; |
|||
} |
|||
|
|||
if (holdings !== undefined) { |
|||
data.holdings = holdings as Prisma.JsonArray; |
|||
} |
|||
|
|||
if (sectors !== undefined) { |
|||
data.sectors = sectors as Prisma.JsonArray; |
|||
} |
|||
|
|||
return data; |
|||
} |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
import { Prisma } from '@prisma/client'; |
|||
import { IsArray, IsOptional } from 'class-validator'; |
|||
|
|||
export class UpdateAssetProfileDataDto { |
|||
@IsArray() |
|||
@IsOptional() |
|||
countries?: Prisma.InputJsonArray; |
|||
|
|||
@IsArray() |
|||
@IsOptional() |
|||
holdings?: Prisma.InputJsonArray; |
|||
|
|||
@IsArray() |
|||
@IsOptional() |
|||
sectors?: Prisma.InputJsonArray; |
|||
} |
|||
Loading…
Reference in new issue