Feature/deprecate portfolio position endpoints (#4648)

* Deprecate api/v1/portfolio/position endpoints

* Update changelog
This commit is contained in:
Thomas Kaul 2025-05-03 20:47:02 +02:00 committed by GitHub
parent 3e963228d6
commit 1bced96460
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 88 additions and 15 deletions

View File

@ -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

View File

@ -50,7 +50,7 @@ export class ImportService {
}: AssetProfileIdentifier): Promise<Activity[]> {
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([

View File

@ -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<PortfolioHoldingResponse> {
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<PortfolioHoldingDetail> {
const holding = await this.portfolioService.getPosition(
): Promise<PortfolioHoldingResponse> {
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<void> {
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<void> {
const holding = await this.portfolioService.getHolding(
dataSource,
impersonationId,
symbol

View File

@ -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<PortfolioHoldingDetail> {
): Promise<PortfolioHoldingResponse> {
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

View File

@ -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<PortfolioHoldingDetail>(
`/api/v1/portfolio/position/${dataSource}/${symbol}`
.get<PortfolioHoldingResponse>(
`/api/v1/portfolio/holding/${dataSource}/${symbol}`
)
.pipe(
map((data) => {
@ -776,7 +776,7 @@ export class DataService {
tags
}: { tags: Tag[] } & AssetProfileIdentifier) {
return this.http.put<void>(
`/api/v1/portfolio/position/${dataSource}/${symbol}/tags`,
`/api/v1/portfolio/holding/${dataSource}/${symbol}/tags`,
{ tags }
);
}

View File

@ -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,

View File

@ -7,7 +7,7 @@ import {
import { Tag } from '@prisma/client';
export interface PortfolioHoldingDetail {
export interface PortfolioHoldingResponse {
averagePrice: number;
dataProviderInfo: DataProviderInfo;
dividendInBaseCurrency: number;