diff --git a/apps/api/src/app/activities/activities.controller.ts b/apps/api/src/app/activities/activities.controller.ts index 589ae48a2c..bd3510fde3 100644 --- a/apps/api/src/app/activities/activities.controller.ts +++ b/apps/api/src/app/activities/activities.controller.ts @@ -18,7 +18,7 @@ import { ActivityResponse } from '@ghostfolio/common/interfaces'; import { permissions } from '@ghostfolio/common/permissions'; -import type { DateRange, RequestWithUser } from '@ghostfolio/common/types'; +import type { RequestWithUser } from '@ghostfolio/common/types'; import { Body, @@ -29,7 +29,6 @@ import { HttpException, Inject, Param, - ParseIntPipe, Post, Put, Query, @@ -38,11 +37,13 @@ import { } from '@nestjs/common'; import { REQUEST } from '@nestjs/core'; import { AuthGuard } from '@nestjs/passport'; -import { Order, Prisma, Type as ActivityType } from '@prisma/client'; +import { Order } from '@prisma/client'; import { parseISO } from 'date-fns'; import { StatusCodes, getReasonPhrase } from 'http-status-codes'; import { ActivitiesService } from './activities.service'; +import { DeleteActivitiesDto } from './delete-activities.dto'; +import { GetActivitiesDto } from './get-activities.dto'; @Controller('activities') export class ActivitiesController { @@ -60,36 +61,30 @@ export class ActivitiesController { @UseGuards(AuthGuard('jwt'), HasPermissionGuard) @UseInterceptors(TransformDataSourceInRequestInterceptor) public async deleteActivities( - @Query('accounts') filterByAccounts?: string, - @Query('activityTypes') filterByTypes?: string, - @Query('assetClasses') filterByAssetClasses?: string, - @Query('dataSource') filterByDataSource?: string, - @Query('range') dateRange?: DateRange, - @Query('symbol') filterBySymbol?: string, - @Query('tags') filterByTags?: string + @Query() query: DeleteActivitiesDto ): Promise { let endDate: Date; let startDate: Date; - if (dateRange) { - ({ endDate, startDate } = getIntervalFromDateRange({ dateRange })); + if (query.range) { + ({ endDate, startDate } = getIntervalFromDateRange({ + dateRange: query.range + })); } const filters = this.apiService.buildFiltersFromQueryParams({ - filterByAccounts, - filterByAssetClasses, - filterByDataSource, - filterBySymbol, - filterByTags + filterByAccounts: query.accounts, + filterByAssetClasses: query.assetClasses, + filterByDataSource: query.dataSource, + filterBySymbol: query.symbol, + filterByTags: query.tags }); - const types = this.parseActivityTypes(filterByTypes); - return this.activitiesService.deleteActivities({ endDate, filters, startDate, - types, + types: query.activityTypes, userId: this.request.user.id }); } @@ -122,51 +117,41 @@ export class ActivitiesController { @UseInterceptors(TransformDataSourceInResponseInterceptor) public async getAllActivities( @Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string, - @Query('accounts') filterByAccounts?: string, - @Query('activityTypes') filterByTypes?: string, - @Query('assetClasses') filterByAssetClasses?: string, - @Query('dataSource') filterByDataSource?: string, - @Query('range') dateRange?: DateRange, - @Query('skip', new ParseIntPipe({ optional: true })) skip?: number, - @Query('sortColumn') sortColumn?: string, - @Query('sortDirection') sortDirection?: Prisma.SortOrder, - @Query('symbol') filterBySymbol?: string, - @Query('tags') filterByTags?: string, - @Query('take', new ParseIntPipe({ optional: true })) take?: number + @Query() query: GetActivitiesDto ): Promise { let endDate: Date; let startDate: Date; - if (dateRange) { - ({ endDate, startDate } = getIntervalFromDateRange({ dateRange })); + if (query.range) { + ({ endDate, startDate } = getIntervalFromDateRange({ + dateRange: query.range + })); } const filters = this.apiService.buildFiltersFromQueryParams({ - filterByAccounts, - filterByAssetClasses, - filterByDataSource, - filterBySymbol, - filterByTags + filterByAccounts: query.accounts, + filterByAssetClasses: query.assetClasses, + filterByDataSource: query.dataSource, + filterBySymbol: query.symbol, + filterByTags: query.tags }); const impersonationUserId = await this.impersonationService.validateImpersonationId(impersonationId); - const types = this.parseActivityTypes(filterByTypes); - const userCurrency = this.request.user.settings.settings.baseCurrency; const { activities, count } = await this.activitiesService.getActivities({ endDate, filters, - skip, - sortColumn, - sortDirection, startDate, - take, - types, userCurrency, includeDrafts: true, + skip: query.skip, + sortColumn: query.sortColumn, + sortDirection: query.sortDirection, + take: query.take, + types: query.activityTypes, userId: impersonationUserId || this.request.user.id, withExcludedAccountsAndActivities: true }); @@ -360,28 +345,4 @@ export class ActivitiesController { } }); } - - private parseActivityTypes(filterByTypes?: string): ActivityType[] { - if (!filterByTypes) { - return []; - } - - const types = filterByTypes.split(','); - - const invalidTypes = types.filter((type) => { - return !Object.values(ActivityType).includes(type as ActivityType); - }); - - if (invalidTypes.length > 0) { - throw new HttpException( - { - error: getReasonPhrase(StatusCodes.BAD_REQUEST), - message: [`Invalid activity type: ${invalidTypes.join(', ')}`] - }, - StatusCodes.BAD_REQUEST - ); - } - - return types as ActivityType[]; - } } diff --git a/apps/api/src/app/activities/delete-activities.dto.ts b/apps/api/src/app/activities/delete-activities.dto.ts new file mode 100644 index 0000000000..7a4f79bfc0 --- /dev/null +++ b/apps/api/src/app/activities/delete-activities.dto.ts @@ -0,0 +1,39 @@ +import { DateRange } from '@ghostfolio/common/types'; + +import { Type as ActivityType } from '@prisma/client'; +import { Transform, TransformFnParams } from 'class-transformer'; +import { IsEnum, IsOptional, IsString } from 'class-validator'; +import { isString } from 'lodash'; + +export class DeleteActivitiesDto { + @IsOptional() + @IsString() + accounts?: string; + + @IsEnum(ActivityType, { each: true }) + @IsOptional() + @Transform(({ value }: TransformFnParams) => { + return isString(value) ? value.split(',') : value; + }) + activityTypes?: ActivityType[]; + + @IsOptional() + @IsString() + assetClasses?: string; + + @IsOptional() + @IsString() + dataSource?: string; + + @IsOptional() + @IsString() + range?: DateRange; + + @IsOptional() + @IsString() + symbol?: string; + + @IsOptional() + @IsString() + tags?: string; +} diff --git a/apps/api/src/app/activities/get-activities.dto.ts b/apps/api/src/app/activities/get-activities.dto.ts new file mode 100644 index 0000000000..65106ff853 --- /dev/null +++ b/apps/api/src/app/activities/get-activities.dto.ts @@ -0,0 +1,27 @@ +import { Prisma } from '@prisma/client'; +import { Type } from 'class-transformer'; +import { IsIn, IsInt, IsOptional, IsString, Min } from 'class-validator'; + +import { DeleteActivitiesDto } from './delete-activities.dto'; + +export class GetActivitiesDto extends DeleteActivitiesDto { + @IsInt() + @IsOptional() + @Min(0) + @Type(() => Number) + skip?: number; + + @IsOptional() + @IsString() + sortColumn?: string; + + @IsIn(['asc', 'desc'] as Prisma.SortOrder[]) + @IsOptional() + sortDirection?: Prisma.SortOrder; + + @IsInt() + @IsOptional() + @Min(0) + @Type(() => Number) + take?: number; +}