diff --git a/CHANGELOG.md b/CHANGELOG.md index f9f4a443e..95d1e00e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Changed + +- Deprecated the endpoint to get a portfolio position in favor of get a holding +- Deprecated the endpoint to update portfolio position tags in favor of update holding tags + ## 2.159.0 - 2025-05-02 ### Added diff --git a/apps/api/src/app/import/import.service.ts b/apps/api/src/app/import/import.service.ts index b6fe0d949..4cb6d46c2 100644 --- a/apps/api/src/app/import/import.service.ts +++ b/apps/api/src/app/import/import.service.ts @@ -50,7 +50,7 @@ export class ImportService { }: AssetProfileIdentifier): Promise { try { const { firstBuyDate, historicalData, orders } = - await this.portfolioService.getPosition(dataSource, undefined, symbol); + await this.portfolioService.getHolding(dataSource, undefined, symbol); const [[assetProfile], dividends] = await Promise.all([ this.symbolProfileService.getSymbolProfiles([ diff --git a/apps/api/src/app/portfolio/portfolio.controller.ts b/apps/api/src/app/portfolio/portfolio.controller.ts index c3e46d50d..5b68f58e0 100644 --- a/apps/api/src/app/portfolio/portfolio.controller.ts +++ b/apps/api/src/app/portfolio/portfolio.controller.ts @@ -20,6 +20,7 @@ import { import { PortfolioDetails, PortfolioDividends, + PortfolioHoldingResponse, PortfolioHoldingsResponse, PortfolioInvestments, PortfolioPerformanceResponse, @@ -56,7 +57,6 @@ import { AssetClass, AssetSubClass, DataSource } from '@prisma/client'; import { Big } from 'big.js'; import { StatusCodes, getReasonPhrase } from 'http-status-codes'; -import { PortfolioHoldingDetail } from './interfaces/portfolio-holding-detail.interface'; import { PortfolioService } from './portfolio.service'; import { UpdateHoldingTagsDto } from './update-holding-tags.dto'; @@ -365,6 +365,32 @@ export class PortfolioController { return { dividends }; } + @Get('holding/:dataSource/:symbol') + @UseInterceptors(RedactValuesInResponseInterceptor) + @UseInterceptors(TransformDataSourceInRequestInterceptor) + @UseInterceptors(TransformDataSourceInResponseInterceptor) + @UseGuards(AuthGuard('jwt'), HasPermissionGuard) + public async getHolding( + @Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string, + @Param('dataSource') dataSource: DataSource, + @Param('symbol') symbol: string + ): Promise { + const holding = await this.portfolioService.getHolding( + dataSource, + impersonationId, + symbol + ); + + if (!holding) { + throw new HttpException( + getReasonPhrase(StatusCodes.NOT_FOUND), + StatusCodes.NOT_FOUND + ); + } + + return holding; + } + @Get('holdings') @UseGuards(AuthGuard('jwt'), HasPermissionGuard) @UseInterceptors(RedactValuesInResponseInterceptor) @@ -583,6 +609,9 @@ export class PortfolioController { return performanceInformation; } + /** + * @deprecated + */ @Get('position/:dataSource/:symbol') @UseInterceptors(RedactValuesInResponseInterceptor) @UseInterceptors(TransformDataSourceInRequestInterceptor) @@ -592,8 +621,8 @@ export class PortfolioController { @Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string, @Param('dataSource') dataSource: DataSource, @Param('symbol') symbol: string - ): Promise { - const holding = await this.portfolioService.getPosition( + ): Promise { + const holding = await this.portfolioService.getHolding( dataSource, impersonationId, symbol @@ -634,7 +663,7 @@ export class PortfolioController { } @HasPermission(permissions.updateOrder) - @Put('position/:dataSource/:symbol/tags') + @Put('holding/:dataSource/:symbol/tags') @UseInterceptors(TransformDataSourceInRequestInterceptor) @UseGuards(AuthGuard('jwt'), HasPermissionGuard) public async updateHoldingTags( @@ -643,7 +672,42 @@ export class PortfolioController { @Param('dataSource') dataSource: DataSource, @Param('symbol') symbol: string ): Promise { - const holding = await this.portfolioService.getPosition( + const holding = await this.portfolioService.getHolding( + dataSource, + impersonationId, + symbol + ); + + if (!holding) { + throw new HttpException( + getReasonPhrase(StatusCodes.NOT_FOUND), + StatusCodes.NOT_FOUND + ); + } + + await this.portfolioService.updateTags({ + dataSource, + impersonationId, + symbol, + tags: data.tags, + userId: this.request.user.id + }); + } + + /** + * @deprecated + */ + @HasPermission(permissions.updateOrder) + @Put('position/:dataSource/:symbol/tags') + @UseInterceptors(TransformDataSourceInRequestInterceptor) + @UseGuards(AuthGuard('jwt'), HasPermissionGuard) + public async updatePositionTags( + @Body() data: UpdateHoldingTagsDto, + @Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string, + @Param('dataSource') dataSource: DataSource, + @Param('symbol') symbol: string + ): Promise { + const holding = await this.portfolioService.getHolding( dataSource, impersonationId, symbol diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index a24809740..26265e8c8 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -41,6 +41,7 @@ import { HistoricalDataItem, InvestmentItem, PortfolioDetails, + PortfolioHoldingResponse, PortfolioInvestments, PortfolioPerformanceResponse, PortfolioPosition, @@ -87,7 +88,6 @@ import { isEmpty } from 'lodash'; import { PortfolioCalculator } from './calculator/portfolio-calculator'; import { PortfolioCalculatorFactory } from './calculator/portfolio-calculator.factory'; -import { PortfolioHoldingDetail } from './interfaces/portfolio-holding-detail.interface'; import { RulesService } from './rules.service'; const asiaPacificMarkets = require('../../assets/countries/asia-pacific-markets.json'); @@ -631,11 +631,11 @@ export class PortfolioService { }; } - public async getPosition( + public async getHolding( aDataSource: DataSource, aImpersonationId: string, aSymbol: string - ): Promise { + ): Promise { const userId = await this.getUserId(aImpersonationId, this.request.user.id); const user = await this.userService.user({ id: userId }); const userCurrency = this.getUserCurrency(user); @@ -927,7 +927,7 @@ export class PortfolioService { } } - public async getPositions({ + public async getHoldings({ dateRange = 'max', filters, impersonationId diff --git a/apps/client/src/app/services/data.service.ts b/apps/client/src/app/services/data.service.ts index 830543dda..41cde8c87 100644 --- a/apps/client/src/app/services/data.service.ts +++ b/apps/client/src/app/services/data.service.ts @@ -13,7 +13,6 @@ import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interface'; import { UpdateOrderDto } from '@ghostfolio/api/app/order/update-order.dto'; -import { PortfolioHoldingDetail } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-holding-detail.interface'; import { SymbolItem } from '@ghostfolio/api/app/symbol/interfaces/symbol-item.interface'; import { DeleteOwnUserDto } from '@ghostfolio/api/app/user/delete-own-user.dto'; import { UserItem } from '@ghostfolio/api/app/user/interfaces/user-item.interface'; @@ -40,6 +39,7 @@ import { OAuthResponse, PortfolioDetails, PortfolioDividends, + PortfolioHoldingResponse, PortfolioHoldingsResponse, PortfolioInvestments, PortfolioPerformanceResponse, @@ -406,8 +406,8 @@ export class DataService { symbol: string; }) { return this.http - .get( - `/api/v1/portfolio/position/${dataSource}/${symbol}` + .get( + `/api/v1/portfolio/holding/${dataSource}/${symbol}` ) .pipe( map((data) => { @@ -776,7 +776,7 @@ export class DataService { tags }: { tags: Tag[] } & AssetProfileIdentifier) { return this.http.put( - `/api/v1/portfolio/position/${dataSource}/${symbol}/tags`, + `/api/v1/portfolio/holding/${dataSource}/${symbol}/tags`, { tags } ); } diff --git a/libs/common/src/lib/interfaces/index.ts b/libs/common/src/lib/interfaces/index.ts index 3e0528fd7..bdf982f55 100644 --- a/libs/common/src/lib/interfaces/index.ts +++ b/libs/common/src/lib/interfaces/index.ts @@ -52,6 +52,7 @@ import type { ImportResponse } from './responses/import-response.interface'; import type { LookupResponse } from './responses/lookup-response.interface'; import type { MarketDataDetailsResponse } from './responses/market-data-details-response.interface'; import type { OAuthResponse } from './responses/oauth-response.interface'; +import { PortfolioHoldingResponse } from './responses/portfolio-holding-response.interface'; import type { PortfolioHoldingsResponse } from './responses/portfolio-holdings-response.interface'; import type { PortfolioPerformanceResponse } from './responses/portfolio-performance-response.interface'; import type { PortfolioReportResponse } from './responses/portfolio-report.interface'; @@ -112,6 +113,7 @@ export { PortfolioChart, PortfolioDetails, PortfolioDividends, + PortfolioHoldingResponse, PortfolioHoldingsResponse, PortfolioInvestments, PortfolioItem, diff --git a/apps/api/src/app/portfolio/interfaces/portfolio-holding-detail.interface.ts b/libs/common/src/lib/interfaces/responses/portfolio-holding-response.interface.ts similarity index 96% rename from apps/api/src/app/portfolio/interfaces/portfolio-holding-detail.interface.ts rename to libs/common/src/lib/interfaces/responses/portfolio-holding-response.interface.ts index 79e4d40dc..cfdc1611d 100644 --- a/apps/api/src/app/portfolio/interfaces/portfolio-holding-detail.interface.ts +++ b/libs/common/src/lib/interfaces/responses/portfolio-holding-response.interface.ts @@ -7,7 +7,7 @@ import { import { Tag } from '@prisma/client'; -export interface PortfolioHoldingDetail { +export interface PortfolioHoldingResponse { averagePrice: number; dataProviderInfo: DataProviderInfo; dividendInBaseCurrency: number;