Browse Source

Support date range in activities endpoint

pull/3190/head
Thomas Kaul 1 year ago
parent
commit
0388a977b3
  1. 5
      apps/api/src/app/benchmark/benchmark.controller.ts
  2. 2
      apps/api/src/app/benchmark/benchmark.module.ts
  3. 8
      apps/api/src/app/order/order.controller.ts
  4. 3
      apps/api/src/app/portfolio/portfolio.controller.ts
  5. 73
      apps/api/src/app/portfolio/portfolio.service.ts
  6. 59
      apps/api/src/helper/portfolio.helper.ts
  7. 1
      apps/client/src/app/pages/portfolio/activities/activities-page.component.ts
  8. 11
      apps/client/src/app/services/data.service.ts

5
apps/api/src/app/benchmark/benchmark.controller.ts

@ -1,6 +1,6 @@
import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service';
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
import { getInterval } from '@ghostfolio/api/helper/portfolio.helper';
import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request.interceptor';
import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response.interceptor';
import type {
@ -35,7 +35,6 @@ import { BenchmarkService } from './benchmark.service';
export class BenchmarkController {
public constructor(
private readonly benchmarkService: BenchmarkService,
private readonly portfolioService: PortfolioService,
@Inject(REQUEST) private readonly request: RequestWithUser
) {}
@ -112,7 +111,7 @@ export class BenchmarkController {
@Param('symbol') symbol: string,
@Query('range') dateRange: DateRange = 'max'
): Promise<BenchmarkMarketDataDetails> {
const { endDate, startDate } = this.portfolioService.getInterval(
const { endDate, startDate } = getInterval(
dateRange,
new Date(startDateString)
);

2
apps/api/src/app/benchmark/benchmark.module.ts

@ -1,4 +1,3 @@
import { PortfolioModule } from '@ghostfolio/api/app/portfolio/portfolio.module';
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module';
import { SymbolModule } from '@ghostfolio/api/app/symbol/symbol.module';
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
@ -22,7 +21,6 @@ import { BenchmarkService } from './benchmark.service';
DataProviderModule,
ExchangeRateDataModule,
MarketDataModule,
PortfolioModule,
PrismaModule,
PropertyModule,
RedisCacheModule,

8
apps/api/src/app/order/order.controller.ts

@ -1,5 +1,6 @@
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
import { getInterval } from '@ghostfolio/api/helper/portfolio.helper';
import { RedactValuesInResponseInterceptor } from '@ghostfolio/api/interceptors/redact-values-in-response.interceptor';
import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request.interceptor';
import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response.interceptor';
@ -8,7 +9,7 @@ import { DataGatheringService } from '@ghostfolio/api/services/data-gathering/da
import { ImpersonationService } from '@ghostfolio/api/services/impersonation/impersonation.service';
import { HEADER_KEY_IMPERSONATION } from '@ghostfolio/common/config';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import type { RequestWithUser } from '@ghostfolio/common/types';
import type { DateRange, RequestWithUser } from '@ghostfolio/common/types';
import {
Body,
@ -84,6 +85,7 @@ export class OrderController {
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId,
@Query('accounts') filterByAccounts?: string,
@Query('assetClasses') filterByAssetClasses?: string,
@Query('range') dateRange: DateRange = 'max',
@Query('skip') skip?: number,
@Query('sortColumn') sortColumn?: string,
@Query('sortDirection') sortDirection?: Prisma.SortOrder,
@ -96,14 +98,18 @@ export class OrderController {
filterByTags
});
const { endDate, startDate } = getInterval(dateRange);
const impersonationUserId =
await this.impersonationService.validateImpersonationId(impersonationId);
const userCurrency = this.request.user.Settings.settings.baseCurrency;
const { activities, count } = await this.orderService.getOrders({
endDate,
filters,
sortColumn,
sortDirection,
startDate,
userCurrency,
includeDrafts: true,
skip: isNaN(skip) ? undefined : skip,

3
apps/api/src/app/portfolio/portfolio.controller.ts

@ -6,6 +6,7 @@ import {
hasNotDefinedValuesInObject,
nullifyValuesInObject
} from '@ghostfolio/api/helper/object.helper';
import { getInterval } from '@ghostfolio/api/helper/portfolio.helper';
import { RedactValuesInResponseInterceptor } from '@ghostfolio/api/interceptors/redact-values-in-response.interceptor';
import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request.interceptor';
import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response.interceptor';
@ -236,7 +237,7 @@ export class PortfolioController {
await this.impersonationService.validateImpersonationId(impersonationId);
const userCurrency = this.request.user.Settings.settings.baseCurrency;
const { endDate, startDate } = this.portfolioService.getInterval(dateRange);
const { endDate, startDate } = getInterval(dateRange);
const { activities } = await this.orderService.getOrders({
endDate,

73
apps/api/src/app/portfolio/portfolio.service.ts

@ -5,7 +5,10 @@ import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interf
import { OrderService } from '@ghostfolio/api/app/order/order.service';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { UserService } from '@ghostfolio/api/app/user/user.service';
import { getFactor } from '@ghostfolio/api/helper/portfolio.helper';
import {
getFactor,
getInterval
} from '@ghostfolio/api/helper/portfolio.helper';
import { AccountClusterRiskCurrentInvestment } from '@ghostfolio/api/models/rules/account-cluster-risk/current-investment';
import { AccountClusterRiskSingleAccount } from '@ghostfolio/api/models/rules/account-cluster-risk/single-account';
import { CurrencyClusterRiskBaseCurrencyCurrentInvestment } from '@ghostfolio/api/models/rules/currency-cluster-risk/base-currency-current-investment';
@ -26,8 +29,7 @@ import {
DATE_FORMAT,
getAllActivityTypes,
getSum,
parseDate,
resetHours
parseDate
} from '@ghostfolio/common/helper';
import {
Accounts,
@ -74,20 +76,10 @@ import {
isBefore,
isSameMonth,
isSameYear,
isValid,
max,
min,
parseISO,
set,
startOfWeek,
startOfMonth,
startOfYear,
subDays,
subYears,
endOfDay,
endOfYear
set
} from 'date-fns';
import { first, isEmpty, last, uniq, uniqBy } from 'lodash';
import { isEmpty, last, uniq, uniqBy } from 'lodash';
import { PortfolioCalculator } from './calculator/twr/portfolio-calculator';
import {
@ -243,49 +235,6 @@ export class PortfolioService {
return dividends;
}
public getInterval(aDateRange: DateRange, portfolioStart = new Date(0)) {
let endDate = endOfDay(new Date());
let startDate = portfolioStart;
switch (aDateRange) {
case '1d':
startDate = max([startDate, subDays(resetHours(new Date()), 1)]);
break;
case 'mtd':
startDate = max([
startDate,
subDays(startOfMonth(resetHours(new Date())), 1)
]);
break;
case 'wtd':
startDate = max([
startDate,
subDays(startOfWeek(resetHours(new Date()), { weekStartsOn: 1 }), 1)
]);
break;
case 'ytd':
startDate = max([
startDate,
subDays(startOfYear(resetHours(new Date())), 1)
]);
break;
case '1y':
startDate = max([startDate, subYears(resetHours(new Date()), 1)]);
break;
case '5y':
startDate = max([startDate, subYears(resetHours(new Date()), 5)]);
break;
case 'max':
break;
default:
// '2024', '2023', '2022', etc.
endDate = endOfYear(new Date(aDateRange));
startDate = max([startDate, new Date(aDateRange)]);
}
return { endDate, startDate };
}
public async getInvestments({
dateRange,
filters,
@ -412,7 +361,7 @@ export class PortfolioService {
exchangeRateDataService: this.exchangeRateDataService
});
const { startDate } = this.getInterval(
const { startDate } = getInterval(
dateRange,
portfolioCalculator.getStartDate()
);
@ -997,7 +946,7 @@ export class PortfolioService {
const userId = await this.getUserId(impersonationId, this.request.user.id);
const user = await this.userService.user({ id: userId });
const { endDate, startDate } = this.getInterval(dateRange);
const { endDate, startDate } = getInterval(dateRange);
const { activities } = await this.orderService.getOrders({
endDate,
@ -1171,7 +1120,7 @@ export class PortfolioService {
)
);
const { endDate, startDate } = this.getInterval(dateRange);
const { endDate, startDate } = getInterval(dateRange);
const { activities } = await this.orderService.getOrders({
endDate,
@ -1479,7 +1428,7 @@ export class PortfolioService {
userId = await this.getUserId(impersonationId, userId);
const { endDate, startDate } = this.getInterval(
const { endDate, startDate } = getInterval(
dateRange,
portfolioCalculator.getStartDate()
);

59
apps/api/src/helper/portfolio.helper.ts

@ -1,4 +1,17 @@
import { resetHours } from '@ghostfolio/common/helper';
import { DateRange } from '@ghostfolio/common/types';
import { Type as ActivityType } from '@prisma/client';
import {
endOfDay,
max,
subDays,
startOfMonth,
startOfWeek,
startOfYear,
subYears,
endOfYear
} from 'date-fns';
export function getFactor(activityType: ActivityType) {
let factor: number;
@ -19,3 +32,49 @@ export function getFactor(activityType: ActivityType) {
return factor;
}
export function getInterval(
aDateRange: DateRange,
portfolioStart = new Date(0)
) {
let endDate = endOfDay(new Date());
let startDate = portfolioStart;
switch (aDateRange) {
case '1d':
startDate = max([startDate, subDays(resetHours(new Date()), 1)]);
break;
case 'mtd':
startDate = max([
startDate,
subDays(startOfMonth(resetHours(new Date())), 1)
]);
break;
case 'wtd':
startDate = max([
startDate,
subDays(startOfWeek(resetHours(new Date()), { weekStartsOn: 1 }), 1)
]);
break;
case 'ytd':
startDate = max([
startDate,
subDays(startOfYear(resetHours(new Date())), 1)
]);
break;
case '1y':
startDate = max([startDate, subYears(resetHours(new Date()), 1)]);
break;
case '5y':
startDate = max([startDate, subYears(resetHours(new Date()), 5)]);
break;
case 'max':
break;
default:
// '2024', '2023', '2022', etc.
endDate = endOfYear(new Date(aDateRange));
startDate = max([startDate, new Date(aDateRange)]);
}
return { endDate, startDate };
}

1
apps/client/src/app/pages/portfolio/activities/activities-page.component.ts

@ -124,6 +124,7 @@ export class ActivitiesPageComponent implements OnDestroy, OnInit {
this.dataService
.fetchActivities({
filters: this.userService.getFilters(),
range: this.user?.settings?.dateRange,
skip: this.pageIndex * this.pageSize,
sortColumn: this.sortColumn,
sortDirection: this.sortDirection,

11
apps/client/src/app/services/data.service.ts

@ -159,12 +159,14 @@ export class DataService {
public fetchActivities({
filters,
range,
skip,
sortColumn,
sortDirection,
take
}: {
filters?: Filter[];
range?: DateRange;
skip?: number;
sortColumn?: string;
sortDirection?: SortDirection;
@ -172,6 +174,10 @@ export class DataService {
}): Observable<Activities> {
let params = this.buildFiltersAsQueryParams({ filters });
if (range) {
params = params.append('range', range);
}
if (skip) {
params = params.append('skip', skip);
}
@ -277,7 +283,10 @@ export class DataService {
startDate: Date;
} & UniqueAsset): Observable<BenchmarkMarketDataDetails> {
let params = new HttpParams();
params = params.append('range', range);
if (range) {
params = params.append('range', range);
}
return this.http.get<BenchmarkMarketDataDetails>(
`/api/v1/benchmark/${dataSource}/${symbol}/${format(

Loading…
Cancel
Save